今天来梳理一下我们常用的排序方式。只是梳理了排序方法中的一部分!!!
可以到这个网站看动画讲解:visualgo,点右下角箭头再点排序就可以看了。
一、冒泡排序
1.1 简介
冒泡排序应该是最简单的一种排序方法之一了吧,它的最坏时间复杂度是 O ( n 2 ) O(n^2) O(n2),最好是 O ( n ) O(n) O(n)。它虽然慢,但程序简单,而且是稳定的排序方式。
1.2 过程
将相邻两个数值比较,小/大的放前面,遍历 n − 1 n-1 n−1次,即可得到有序数组。
1.3 原理
每一趟遍历找出当前乱序数列中最大元素并逐渐移动到末尾,从而完成排序,“冒泡”也因此得来。
1.4 程序(从小到大)
int* sort(int* a,int n){
for(int i=0;i<n-1;++i){
for(int j=1;j<n-i;++j){
if(a[j]<a[j-1]){
swap(a[j],a[j-1]);
}
}
}
return a;
}
二、选择排序
2.1 简介
选择排序是一种简单直观的排序算法。它的最好和最坏时间复杂度都是 O ( n 2 ) O(n^2) O(n2)。
2.2 过程
先建立一个变量i,遍历 0 ~ n − 1 0~n-1 0~n−1,然后在此变量之后(包含此变量)的所有元素中找到最小/大值,与a[i]交换,遍历完即可排序完成。
2.3 原理
遍历每个元素,找到剩下序列排序完成时应在第一个的元素放在当前位置。
2.4 程序(从小到大)
int* sort(int* a,int n){
for(int i=0;i<n-1;++i){
int minn=1E9,mini=0;
for(int j=i;j<n;++j){
if(minn>a[j]){
minn=a[j];
mini=j;
}
}
swap(a[i],a[mini]);
}
return a;
}
三、直接插入排序
3.1 简介
直接插入排序也很简单易懂,它是遍历数组,将数据插入到合适的位置,从而完成排序。它的最好时间复杂度是 O ( n ) O(n) O(n),最坏时间复杂度是 O ( n 2 ) O(n^2) O(n2)。除了直接插入排序,还有折半插入排序、希尔排序、和表插入排序三种,他们都是插入排序中的一种,在此不再赘述。
3.2 过程
遍历数组,从当前位置的前一位开始while循环找到第一个小于/大于当前元素的位置,同时将路上的所有元素往后移动,如果没找到就在0那里停下。最终将当前元素放到while循环中遍历的变量的位置,即可完成一次遍历。遍历完成之后排序完成。
3.3 程序(从小到大)
int* sort(int* a,int n){
for(int i=0;i<n;++i){
int t=a[i],cur=i;
while((cur>0)&&(a[cur-1]>=t)){
a[cur]=a[cur-1];
cur--;
}
a[cur]=t;
}
return a;
}
四、桶排序
4.1 简介
桶排序也比较简单,程序是非常的好写。它的时间复杂度达到了惊人的 O ( n ) O(n) O(n),但别以为它就没有缺点了,它的空间复杂度也很惊人,达到了 O ( n + k ) O(n+k) O(n+k),其他的排序不都是 O ( n ) O(n) O(n)的时间复杂度吗,它在这个基础上增加了一个k,代表数据范围,也就是说如果有10个0~100的数,它至少需要110个int的空间…
4.2 过程
其实桶排序正常返回的是一个数组, a i a_i ai是i在原数组中出现的次数。如何制造出这个数组呢?只要遍历原数组,每次都 a [ s [ i ] ] + + a[s[i]]++ a[s[i]]++就可以了。那虽然很简单,要这个数组有什么用呢?其实,我们得到了这个数组,如果我们还想要得到排序序列,就很好弄了。如下:
//s是原数组,a是排序后的数组。
int cur=0;
for(int i=0;i<1000000;++i){
while(a[i]){
a[i]--;
s[cur]=i;
cur++;
}
}
4.3 程序(从小到大,仅为生成数组字典的部分,拆开部分见上段)
void sort(int* a,int n,int* s){
for(int i=0;i<n;++i){
s[a[i]]++;
}
}
//可以看到,这段代码还是很短的
五、快速排序
5.1 简介
快速排序终于出场了!它是对冒泡排序的一种改进。它的平均时间复杂度是 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n),而最坏时间复杂度是 O ( n 2 ) O(n^2) O(n2)。
5.2 过程
快速排序也分为很多次遍历,一次遍历的方法如下:先选取一个基准值,通过操作使得都小于基准值的元素处于一个区间内,大于基准值的元素处于一个区间内,再用递归分别遍历左区间和右区间。遍历完成后即可完成排序。(当区间已经只剩1个数时函数直接结束,不继续递归)
5.3 程序(从小到大)
void sort(int* a,int start,int end){
if(start>=end){
return;
}
int pivot=a[start];
int l=start+1,r=end;
while(l<=r){
while(a[l]<=pivot&&l<=r){
l++;
}
while(a[r]>=pivot&&l<=r){
r--;
}
if(l>=r){
break;
}
swap(a[l],a[r]);
}
swap(a[start],a[r]);
sort(a,start,r-1);
sort(a,r+1,end);
}
六、归并排序
6.1 简介
归并排序是一种建立在归并操作上的排序算法,它将数组分成若干个小段再组合从而完成排序。它的时间复杂度在所有情况下都是 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n),空间复杂度是 O ( n ) O(n) O(n)。
6.2 过程
归并排序的一次遍历:先将数组分为两半,分别遍历,最后组合在一起。所以我们很容易猜到它也要用递归。那么合并在一起怎么弄呢?我们只要定义两个指针,(从小到大的话)哪个小,就放入最终数组里,同时指针自增,判断指针的范围即可。合并函数如下(从小到大):
void merge(int* s,int start,int end,int mid){
int a[mid-start+10]={};
int b[end-mid+10]={};
int cur=0;
for(int i=start;i<=mid;++i){
a[cur++]=s[i];
}
cur=0;
for(int i=mid+1;i<=end;++i){
b[cur++]=s[i];
}
cur=start;
int l=0,r=0;
for(;l<mid-start+1&&r<end-mid;){
if(a[l]<=b[r]){
s[cur++]=a[l];
l++;
}else{
s[cur++]=b[r];
r++;
}
}
if(l<mid-start+1){
for(int i=l;i<mid-start+1;++i){
s[cur++]=a[i];
}
}else if(r<end-mid){
for(int i=r;i<end-mid;++i){
s[cur++]=b[i];
}
}
}
6.3 程序(从小到大,仅为递归排序部分,合并部分见上段)
void sort(int* a,int start,int end){
if(start<end){
int mid=(start+end)>>1;
sort(a,start,mid);
sort(a,mid+1,end);
merge(a,start,end,mid);
}
}
七、结尾
排序算法很多,(再次强调)在此仅是列举了其中的一小部分。本文中的代码均为手写排序的代码,如果没有特殊需求也可用C++标准库中的sort函数,这个函数的用法在此不再赘述,可自行查阅资料。