9种排序算法总结

排序算法可以说是计算机专业学生要学习的最基础的算法,但其实也是最重要的,现在大部分互联网公司笔试面试也都会涉及到排序算法的知识。除了了解思想之外,还应该动手写一写,分析一些具体思路、时间复杂度、空间复杂度和稳定性等。

我们面试讨论小分队也简单讨论了一下排序算法,为了加深记忆,我自己也动手写了一些代码(Linux平台写的,自己测试是通过了),并做一些分析(由于水平较水,代码可能有误!)。

9种排序算法分别为:选择排序、冒泡排序、插入排序、希尔排序、归并排序、堆排序、快速排序、计数排序、基数排序!

1. 选择排序

基本思想:从第一个位置开始依次选择该位置的元素,第i次扫描就可以选出第i小的元素,思想很简单,现在用的较少。

特点:平均时间复杂度O(n^2),最坏时间复杂度O(n^2),额外空间O(1),不稳定排序(举例:序列5 8 5 2 9, 第一遍选择第1个元素5会和2交换,原序列中2个5的相对前后顺序就被破坏了),n较小时较好!

代码:

[cpp]  view plain copy
  1. void select_sort(int *a, int n)  
  2. {  
  3.     for(inti = 1; i <= n; i++) {  
  4.         intmin_pos = i;  
  5.         for(intj = i+1; j <= n; j++)  
  6.             if(a[j] < a[min_pos])  
  7.                 min_pos = j;  
  8.                                                                                                                                                                                                                     
  9.         if(min_pos != i) {  
  10.             inttemp = a[i];  
  11.             a[i] = a[min_pos];  
  12.             a[min_pos] = temp;  
  13.         }     
  14.     }     
  15. }  

2. 冒泡排序

基本思想:顾名思义,每一趟都通过相邻元素两两比较,通过交换将较小的元素往前移动,一趟下来就可以将最小的元素(气泡)移动到最前面。一般会加一个标志flag,若一趟扫描没有任何元素交换,则说明序列已经有序,flag为false,直接退出。

特点:平均时间复杂度O(n^2),最坏时间复杂度O(n^2),额外空间O(1),稳定排序(因为比较和交换都是两相邻元素,相等时不交换),n较小时较好!

代码:

[cpp]  view plain copy
  1. void bubble_sort(int *a, int n)  
  2. {  
  3.     boolflag =false;  
  4.     for(inti = 1; i <= n; i++) {  
  5.         for(intj = n; j > i; j--)  
  6.             if(a[j] < a[j-1]) {  
  7.                 inttemp = a[j];  
  8.                 a[j] = a[j-1];  
  9.                 a[j-1] = temp;  
  10.                 flag =true;  
  11.             }  
  12.         if(!flag)  
  13.             return;  
  14.     }  
  15. }  

3. 插入排序

基本思想:假定一个已排好序的序列和一个元素,只需将该元素从序列末尾向前比较,找到第一个小于它的序列元素,排在其之后即可。思想类似于玩扑克牌时整理牌面。

特点:平均时间复杂度O(n^2),最坏时间复杂度O(n^2),额外空间O(1),稳定排序(比较元素和序列时,找到序列中相等元素的话,排在其之后),序列大部分已排好序时(时间复杂度可提升至O(n))较好!

代码:

[cpp]  view plain copy
  1. void insert_sort(int *a, int n)  
  2. {  
  3.     inttemp;  
  4.     for(inti =2; i <= n; i++) {  
  5.         intj = i - 1;  
  6.         temp = a[i];  
  7.         while(j >= 1) {  
  8.             if(a[j] > temp) {  
  9.                 a[j+1] = a[j];  
  10.                 j--;  
  11.             }else  
  12.                 break;  
  13.         }  
  14.         a[j+1] = temp;  
  15.     }  
  16. }  

4. 希尔排序

基本思想:插入排序的升级版(根据其特点:序列大部分已排好序时效率很高),将数据分为不同的组,先对每一组进行排序,然后对所有元素进行一次排序(即最后步长必须为1),步长的选择是递减的,比如5、3、1,现在一般使用D.E.Knuth分组方法(n很大是,用h(n+1)=3h(n)+1来分组,即1、4、13......)。

特点:平均时间复杂度O(n*logn),最坏时间复杂度O(n^s)(1<s<2),额外空间O(1),不稳定排序(相等元素在不同组里,交换后相对顺序可能改变)!

代码:

[cpp]  view plain copy
  1. void shell_sort(int *a, int n)  
  2. {   //我这里步长为5、3、1,仅为举例  
  3.     for(intgap = 5; gap > 0; gap -= 2)  
  4.         for(inti = gap + 1; i <=n; i++) {  
  5.             intj = i - gap;  
  6.             inttemp = a[i];  
  7.             while(j >= 1) {  
  8.                 if(a[j] > temp) {  
  9.                     a[j + gap] = a[j];  
  10.                     j -= gap;  
  11.                 }else  
  12.                     break;  
  13.             }  
  14.             a[j+gap] = temp;  
  15.         }  
  16. }  

5. 归并排序

基本思想:分治的思想,就是用递归先将序列分解成只剩一个元素的子序列,然后逐渐向上进行合并,每次合并过程就是将两个内部已排序的子序列进行合并排序,只需O(n)时间。

特点:平均时间复杂度O(n*logn),最坏时间复杂度O(n*logn),额外空间O(n)(另外需要一个数组),稳定排序,当n较大时较好(当也不能太大,用了递归就要考虑栈溢出)!

代码:

[cpp]  view plain copy
  1. int b[MAX] = {0};  
  2.                                                                                                                                  
  3. void merge(int *a,intlow, int mid, inthigh)  
  4. {  
  5.     inti = low, j = mid + 1;//左边和右边的初始位置  
  6.     intk = i;  
  7.     while(i <= mid && j <= high) {  
  8.         if(a[i] <= a[j]) {  
  9.             b[k++] = a[i];  
  10.             i++;  
  11.         }else{  
  12.             b[k++] = a[j];  
  13.             j++;  
  14.         }  
  15.     }  
  16.     while(i <= mid){  
  17.         b[k++] = a[i++];  
  18.     }  
  19.     while(j <= high){  
  20.         b[k++] = a[j++];  
  21.     }  
  22.                                                                                                                                  
  23.     for(intx = 1, i = low; x <= high-low+1; x++, i++)  
  24.         a[i] = b[i];  
  25. }  
  26.                                                                                                                                  
  27. voidmerge_sort(int*a,int low, int high)  
  28. {  
  29.     intmid;  
  30.     if(low < high) {  
  31.         mid = (low + high) / 2;  
  32.         merge_sort(a, low, mid);  
  33.         merge_sort(a, mid+1, high);  
  34.         merge(a, low, mid, high);  
  35.     }  
  36. }  

6. 堆排序

基本思想:利用最大堆的性质——父节点拥有最大值,所以不断的将堆的根节点与最后节点交换,减小堆长度,然后再恢复堆性质,堆排序主要就是建立最大堆和不断恢复堆性质两个过程。堆排序不需要用到递归,所以适合海量数据处理,同时堆还可以用于优先级队列。

特点:平均时间复杂度O(n*logn),最坏时间复杂度O(n*logn),额外空间O(1),不稳定排序(涉及根节点与最后节点的交换,可能会破坏两相等元素的相对位置!),当n较大时较好(海量数据)!

代码:

[cpp]  view plain copy
  1. void max_heapify(int *a, int p, int n)  
  2. {  
  3.     intleft = 2 * p;  
  4.     intright = 2 * p + 1;  
  5.     intlarge = p;   
  6.     if(left <= n && a[left] > a[p])  
  7.         large = left;  
  8.     if(right <= n && a[right] > a[large])  
  9.         large = right;  
  10.                                                                                                                   
  11.     if(large != p) {  
  12.         inttemp = a[p];  
  13.         a[p] = a[large];  
  14.         a[large] = temp;  
  15.         max_heapify(a, large, n);  
  16.     }  
  17. }  
  18.                                                                                                       
  19. voidheap_sort(int*a,int n)  
  20. {  
  21.     //build_max_heap  
  22.     for(inti = n/2; i > 0; i--)  
  23.         max_heapify(a, i, n);  
  24.                                                                                                       
  25.     inttemp;  
  26.     while(n > 1){  
  27.         temp = a[n];  
  28.         a[n] = a[1];  
  29.         a[1] = temp;  
  30.                                                                                                               
  31.         --n;  
  32.         max_heapify(a, 1, n);  
  33.     }  
  34. }  

7. 快速排序

基本思想:快排是目前使用最多的排序算法,每次都是先选择一个位置的元素(可以为序列的最左或最右位置)作为中间值,将比其小的元素放在其左边,比其大的元素放在右边,然后递归对其左边和右边的子序列进行相同操作,直到子序列为单个元素。

特点:平均时间复杂度O(n*logn),最坏时间复杂度O(n^2)(序列基本有序时,退化为冒泡排序),额外空间O(logn),不稳定排序(举例:序列为 5 3 3 4 3 8 9 10 11, 现在中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱),当n较大时较好(当也不能太大,用了递归就要考虑栈溢出)!

代码:

[cpp]  view plain copy
  1. void quick_sort(int *a, int p, int r)  
  2. {  
  3.     if(p < r) {  
  4.         inttemp;  
  5.         intx = a[r];  
  6.         inti = p - 1;  
  7.         for(intj = p; j < r; j++)  
  8.             if(a[j] < x) {  
  9.                 i++;  
  10.                 temp = a[j];  
  11.                 a[j] = a[i];  
  12.                 a[i] = temp;  
  13.             }  
  14.                                                                                   
  15.         temp = a[i+1];  
  16.         a[i+1] = a[r];  
  17.         a[r] = temp;  
  18.         quick_sort(a, p, i);  
  19.         quick_sort(a, i+2, r);  
  20.     }  
  21. }  

8. 计数排序

基本思想:假定输入是有一个小范围内的整数构成的(比如年龄等),利用额外的数组去记录元素应该排列的位置,思想比较简单,看代码即可了解。

特点:在一定限制下时间复杂度为O(n),额外空间O(n)(需要两个数组),稳定排序!

代码:

[cpp]  view plain copy
  1. int b[MAX] = {0};  
  2. int c[MAX] = {0};  
  3.                                                              
  4. void counting_sort(int *a, int n)  
  5. {  
  6.                                                              
  7.     for(inti=1; i <= n; i++)  
  8.         c[a[i]]++;   //c[i]包含等于i的元素个数  
  9.                                                              
  10.     for(inti=1; i < MAX; i++)  
  11.         c[i] += c[i-1];//c[i]包含小于等于i的元素个数  
  12.                                                              
  13.     for(inti = n; i>0; i--){  
  14.         b[c[a[i]]] = a[i];  
  15.         c[a[i]]--;  
  16.     }  
  17.     for(inti = 1; i <=n; i++)  
  18.         a[i] = b[i];  
  19. }  

9. 基数排序

基本思想:只适用于整数排序,确定序列中元素的最大位数d,只要进行d次循环,从低位开始根据相应位置的数进行排序。(我的代码中具体排序是参考了计数排序,数据结构中还可以用链式相关的方法)。

特点:在一定限制下时间复杂度为O(n),额外空间O(n)(需要两个数组),稳定排序!

代码:

[cpp]  view plain copy
  1. int b[MAX] = {0};  
  2. int counter[10] = {0};  
  3. int get_value(int v, int d) //获取第d位上的值  
  4. {  
  5.     for(inti = 1; i < d; i++)   
  6.         v = v/10;  
  7.     returnv%10;  
  8.                                         
  9. }  
  10. //只能排序d位的十进制数  
  11. voidradix_sort(int*a,int n, int d)  
  12. {  
  13.     intx;  
  14.     for(intk = 1; k <= d; k++) {  
  15.         for(inti = 0; i < 10; i++)  
  16.             counter[i] = 0;//注意,一定要清零  
  17.         for(inti = 1; i <= n; i++) {  
  18.             x = get_value(a[i], k);  
  19.             counter[x]++;  
  20.         }  
  21.                                                 
  22.         for(inti = 1; i < 10; i++)  
  23.             counter[i] += counter[i-1];  
  24.         for(inti = n; i > 0; i--) {  
  25.             x = get_value(a[i], k);  
  26.             b[counter[x]] = a[i];  
  27.             counter[x]--;  
  28.         }  
  29.         for(inti = 1; i <= n; i++)  
  30.             a[i] = b[i];  
  31.     }  
  32. }  

排序总结

稳定性:选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法,而冒泡排序、插入排序、归并排序和基数排序是稳定的排序算法。

快速排序算法使用最广泛,大数据量时适合使用快速排序、归并排序和堆排序,需要O(n)时间复杂度时(当然要考虑数值范围的限制),可以考虑使用计数排序、基数排序、桶排序(上面未介绍,思想很简单,假设数据分布均匀!)等。

最后是我用来测试排序算法的main函数,非常简单!

[cpp]  view plain copy
  1. #include <iostream>  
  2. usingnamespacestd;  
  3.                               
  4. constintMAX = 255;  
  5.                           
  6. int main ()  
  7. {  
  8.     intn;  
  9.     inta[MAX];  
  10.     cin >> n;  
  11.     for(inti = 1; i <= n; i++)  
  12.         cin >> a[i];  
  13.                           
  14.     cout <<"Before sort:";  
  15.     for(inti = 1; i <= n; i++)  
  16.         cout << a[i] <<" ";  
  17.     cout << endl;  
  18.     //radix_sort(a, n, 2);  
  19.     //select_sort(a, n);  
  20.     //insert_sort(a, n);  
  21.     //bubble_sort(a, n);  
  22.     //quick_sort(a, 1, n);  
  23.     //heap_sort(a, n);  
  24.     //merge_sort(a, 1, n);  
  25.     //counting_sort(a, n);  
  26.     //shell_sort(a, n);  
  27.     cout <<"Sort:";  
  28.     for(inti = 1; i <= n; i++)  
  29.         cout << a[i] <<" ";  
  30.     cout << endl;  
  31.     return0;  
  32. }  

排序算法基本上就总结如上,告诫自己,不能死记硬背!要理解思想,同时要注意实现上的一些技巧!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值