排序算法汇总(插入、选择、交换、归并)
- 稳定性
待排序数据中任意两个排序关键字相同的数据元素Di,Dj(Ki=Kj),排序前Di排在Dj前;如果在排序算法执行完成后得到的有序序列中,Di也Dj前面,则称该排序算法是稳定的。
稳定的算法:直接插入排序、冒泡排序、两路合并排序
不稳定的算法:希尔排序、选择排序、堆排序、快速排序
排序算法 |
最好情况 |
最坏情况 |
平均情况 |
直接插入排序 | O(n) | O(n^2) | O(n^2) |
希尔排序 | O(n^1.25) | ||
简单选择排序 | O(n^2) | O(n^2) | O(n^2) |
堆排序 | O(nlogn) | O(nlogn) | O(nlogn) |
冒泡排序 | O(n) | O(n^2) | O(n^2) |
快速排序 | O(nlogn) | O(n^2) | O(nlogn) |
两路合并排序 | O(nlogn) | O(nlogn) | O(nlogn) |
直接插入排序
从只包含一个数据元素的有序序列开始,不断地将待排序数据元素有序地插入这个有序序列中,直到有序序列包含了所有待排序数据元素为止。
对于一个长度为n的关键字序列a1,a2,a3,a4,a5...an,第一个元素a1作为子序列开始,从第二个元素开始逐个插入元素。此时插入ai,从序列的第一个元素开始比较是否小于ai,如果小于,则从此位置开始将后面的序列元素逐个后移一位,将ai插入在该位置。最好情况是该序列具有递增性,所以每次只需要放置在末尾位置。(注意:此时如果相等,则ai会放置在该元素后一位,所以会保持序列的稳定性)。
希尔排序
希尔排序是一种直接插入排序的优化,首先将序列按照所在位置,将间隔di相同的分在一组,在组内进行直接插入排序,然后再逐渐缩小di直到等于1。这样做的是在完成最后一次直接插入排序之前,增加了序列的有序性,降低时间复杂度。(注意:希尔排序不是稳定排序)
简单选择排序
每一趟排序,找到待排序序列中关键字最小的数据元素,将其与待排序序列中第一个数据元素交换位置,并将其从下一趟待排序序列中移出。最先开始待排序序列为0->n-1,然后找到序列中最小的元素,将它放置在0号位置,然后此时待排序序列为1->n-1,再寻找此时的最小元素,放置在1号位置,并将调整待排序序列长度。重复操作,直到找到最后一个元素。(注意:简单选择排序是非稳定排序)
堆排序
在进行排序前,先将序列构造成为一个最大堆(使用插入堆算法,每一次插入都保持最大堆的性质),然后逐个输出当前堆顶元素(使用删除算法),每次堆顶离开当前堆后,对剩余元素重新调整成堆,直到堆中只剩下一个元素。
冒泡排序
从前向后不断交换相邻逆序(关键字递减)数据元素,重复该过程,直到任意相邻数据元素都不再逆序排列为止。(注意:每一趟遍历,将一个最大的数移到序列末尾。如果两个数相等,则不需要交换顺序,所以是稳定排序)
优化1:定义一个变量isSwap,在每一趟之前都设置为false,如果该趟中有交换发生,则置为true。在一趟遍历结束后,如果isSwap仍然为false,则直接退出循环。
优化2:定义一个变量n来保存一趟交换中最后一次发生交换的位置,并把它传递给下一趟交换。假如有一个长度为50的数组,在一趟交换后,最后发生交换的位置是10,那么这个位置之后的40个数必定已经有序了,记录下这位置,下一趟交换只要从数组头部到这个位置就可以了。
快速排序
在待排序序列中选择一个分割元素,将待排序序列中所有比分割元素关键字小或相等的元素移动到分割元素左侧位置,将待排序序列中所有比分割元素大的元素移动到分割元素右侧位置,然后将分割元素左侧所有元素看作一个待排序序列,重复上述过程,直到这些元素完全有序,最后将分割元素右侧所有元素看作一个待排序序列,重复上述过程,直到这些元素完全有序。
分别从数组的两端扫描数组,设两个指示标志:low指向起始位置,high指向末尾。首先让i=low,j=high+1,然后i和j分别前进,向彼此逼近,当它们都不能前进时,交换停止处所指示的数据元素,再继续前进,直到相遇,交换low和j,此时j所在位置就将序列换分为两个序列。
int Partition(List *list,int low,int high) { int i=low,j=high+1; Entry pivot=list->D[low];///pivot是分割元素,选择第一个元素为分割元素 do{ do i++;while(list->D[i].key<pivot.key); do j++;while(list->D[j].key>pivot.key); if(i<j) Swap(list->D,i,j); }while(i<j); Swap(list->D,low,j); return j;///此时j是分割元素下标 } void Quicksort(List *list,int low, int high) { int k; if(low<high) { k=Partition(list,low,high); Quicksort(list,low,k-1); Quicksort)list,k+1,high); } }
问题1:如果数据是有序或者逆序时,从数组的一端或者另一端选择数据项会导致无法平均的划分序列,退化为冒泡排序
优化:中间值法——在左边界low,右边界high和中间下标mid所代表的元素之间选择一个作为基准点,选择方法为找到这三个值中中间那个作为基准点。
问题2:如果待排序列中重复元素过多,也会大大影响排序的性能
优化:三路快排——之前的快速排序算法都是将序列分成<=v和>v或者是<v和>=v的两个部分,而三路快速排序是将序列分成三个部分:<v、=v、>v (参考:https://www.cnblogs.com/deng-tao/p/6536302.html)
两路合并排序
初始时将待排序的n个数据元素看作n个待合并有序序列,每个序列中只包含一个数据元素;将每m个待合并序列合并成一个大的有序序列,重复合并过程,直到所有的数据元素都属于同一个序列为止(当m=2的合并排序称为两路合并排序)
合并过程:设置i,j和p三个指针,其初值分别指向这三个记录区的起始位置。合并时依次比较R[i]和R[j]的关键字,取关键字较小的记录复制到R1[p]中,然后将被复制记录的指针i或j加1,以及指向复制位置的指针p加1。重复这一过程直至两个输入的子文件有一个已全部复制完毕(不妨称其为空),此时将另一非空的子文件中剩余记录依次复制到R1中即可。(注意:合并排序是稳定排序)