排序算法总结

好记性不如烂笔头o(^▽^)o

前言
  记得之前毕业找工作的时候好好总结过,但是,工作中基本不怎么用到这些排序算法,所以久而久之就忘了。以前写的文章链接(包括详细的思路、图和完整代码):
《直接选择排序》
《冒泡排序算法》
《希尔排序算法》
《快速排序算法》
《直接插入排序》

  其实每种看了后很容易混淆和记不住,所以这里就对这些排序算法进行归类和总结,主要涉及十大排序算法,包括算法思路和算法分析。最后给出排序算法的选择,让你在面对排序时心里有数。

正文

排序算法总结

分类总结

排序算法按照是否涉及数据的内、外存交换分为内排序和外排序。

排序过程中整个文件都放在内存中处理,排序时不涉及数据的内外存交换,称为内排序,使用于记录个数不是很多的小文件,反之为外排序。

按照是否进行比较分为比较排序和非比较排序。

非比较排序:桶
比较排序:其他

按照性能可分为稳定排序和不稳定排序。

稳定排序:插入、冒泡、归并、桶、二叉树排序、基数排序
不稳定排序:选择、快速、堆、希尔、计数

  那么,什么是稳定的排序?
  假设在排序中存在多个相同关键字的记录,经排序后,这些记录的相对位置保持不变,则是稳定的排序。

下面表格给出常见排序算法的一些复杂度统计:

排序方法平均情况最好情况最坏情况辅助空间稳定性
冒泡排序O(n^2)O(n)O(n^2)O(1)稳定
选择排序O(n^2)O(n^2)O(n^2)O(1)稳定
直接插入排序O(n^2)O(n)O(n^2)O(1)稳定
希尔排序O(nlogn) ~ O(n^2)O(n^1.3)O(n^2)O(1)不稳定
堆排序O(nlogn)O(nlogn)O(nlogn)O(1)不稳定
归并排序O(nlogn)O(nlogn)O(nlogn)O(1)稳定
快速排序O(nlogn)O(nlogn)O(n^2)O(logn) ~ O(n)不稳定

  根据上表,按平均时间将排序分为四类:
(1)平方阶(O(n2))排序

一般称为简单排序,例如直接插入、直接选择和冒泡排序;

(2)线性对数阶(O(nlgn))排序

如快速、堆和归并排序;

(3)O(n1+£)阶排序

£是介于0和1之间的常数,即0<£<1,如希尔排序;

(4)线性阶(O(n))排序

如桶、箱和基数排序。

各排序算法思想

十大排序算法:
一、冒泡(Bubble)排序——相邻交换
二、选择排序——每次最小/大排在相应的位置
三、插入排序——将下一个插入已排好的序列中
四、希尔排序——缩小增量
五、归并排序
六、快速排序
七、堆排序
八、计数排序
九、桶排序
十、基数排序

冒泡排序

基本思想:两两比较待排序记录的关键字,发现两个记录的次序相反时即进行交换,直到没有反序的记录为止。

  因为每一趟排序都使有序区增加了一个气泡,在经过n-1趟排序之后,有序区中就有n-1个气泡,而无序区中气泡的重量总是大于等于有序区中气泡的重量,所以整个冒泡排序过程至多需要进行n-1趟排序。
  若在某一趟排序中未发现气泡位置的交换,则说明待排序的无序区中所有气泡均满足轻者在上,重者在下的原则,因此,冒泡排序过程可在此趟排序后终止。

  算法分析:时间复杂度最好的情况为O(n),平均和最坏的情况是O(n^2)。

选择排序

基本思想是:每一趟从待排序的记录中选出关键字最小的记录,顺序放在已排好序的子文件的最后,直到全部记录排序完毕。

  初始状态:无序区为R[1..n],有序区为空,第i趟有序区和无序区分别为R[1..i-1]和R[i..n]。无论文件初始状态如何,在第i趟排序中选出最小关键字的记录,需做n-i次比较,因此,总的比较次数为:n(n-1)/2=0(n2)。

  算法分析:最差平均时间复杂度都是O(n^2),是就地排序,选择排序与冒泡排序的时间复杂度相同,但简单选择排序的性能要比冒泡优。选择是通过下标改动来排,冒泡是是直接交换,冒泡的交换次数要比选择多。

插入排序

基本思想:每次将一个待排序的记录,按照其关键字大小插入到已经排好序的子序列的适当位子,知道全部记录插入完成为止。

  假设待排序的记录都存储在R[n]中,首先把R[1]自成一个有序区,R[2..n]为无序区,然后将R[2..n]中的记录依次插入到R[1..i]中,直到生成含有n个记录的有序区。在插入的某一中间时刻,存在两个区,一个是R[1..i-1]有序区,一个R[i..n]无序待排序的区。

  算法分析:最差、平均都是O(n^2),最好是O(n)。

希尔排序

希尔排序(Shell Sort)是插入排序的一种。先取一个小于n的整数d1作为第一个增量,所有距离为dl的倍数的记录放在同一个组中。先在各组内进行直接插人排序;然后,取第二个增量d2 < d1重复上述的分组和排序,直至所取的增量dt=1(dt<dt-l<…<d2<d1),即所有记录放在同一组中进行直接插入排序为止。实质上是一种分组插入方法。

算法分析:时间复杂度O(nlogn)

1.增量序列的选择
Shell排序的执行时间依赖于增量序列。好的增量序列的共同特征:
  ① 最后一个增量必须为1;
  ② 应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况。
有人通过大量的实验,给出了目前较好的结果:当n较大时,比较和移动的次数约在n^l.25到1.6n^1.25之间。
2.Shell排序的时间性能优于直接插入排序,原因:
  ①当文件初态基本有序时直接插入排序所需的比较和移动次数均较少。
  ②当n值较小时,n和n^2的差别也较小,即直接插入排序的最好时间复杂度O(n)和最坏时间复杂度0(n^2)差别不大。
  ③在希尔排序开始时增量较大,分组较多,每组的记录数目少,故各组内直接插入较快,后来增量di逐渐缩小,分组数逐渐减少,而各组的记录数目逐渐增多,但由于已经按di-1作为距离排过序,使文件较接近于有序状态,所以新的一趟排序过程也较快。
  
  因此,希尔排序在效率上较直接插人排序有较大的改进。

归并排序

  归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
  将二个有序数列合并:这个非常简单,只要比较二个数列的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。时间复杂度O(N)。

  归并排序,其的基本思路就是将数组分成二组A,B,如果这二组组内的数据都是有序的,那么就可以很方便的将这二组数据进行排序。可以将A,B组各自再分成二组。依次类推,当分出来的小组只有一个数据时,可以认为这个小组组内已经达到了有序,然后再合并相邻的二个小组就可以了。这样通过先递归的分解数列,再合并数列就完成了归并排序。一共要logN步。

  算法分析:设数列长为N,将数列分开成小数列一共要logN步,每步都是一个合并有序数列的过程,时间复杂度可以记为O(N),故一共为O(N*logN)。因为归并排序每次都是在相邻的数据中进行操作,所以归并排序在O(N*logN)的几种排序方法(快速排序,归并排序,希尔排序,堆排序)也是效率比较高的。

快速排序

  基本思想:快速排序是一种划分交换排序。它采用了一种分治的策略,通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。

1.先从数列中取出一个数作为基准数。
2.分区过程,将比这个数大的数全放到它的右边,小于或等于它的数全放到它的左边。
3.再对左右区间重复第二步,直到各区间只有一个数。
算法分析:平均时间复杂度为O(n*log n),最坏情况下O(n^2),空间复杂度O(log n)。

  算法分析:与归并排序不同,这两个子问题并不保证具有相等的大小,而是在适当的位置进行并且非常有效。
  初值选取问题:错误的方法是选取第一个元素;随机选取,策略安全,但减少不了其余部分的平均时间;三数中值分割法,选取左端、右端、中心位置上三个元素的中值。

堆排序

  举例最小堆来说,首先可以看到堆建好之后堆中第0个数据是堆中最小的数据。取出这个数据再执行下堆的删除操作。这样堆中第0个数据又是堆中最小的数据,重复上述步骤直至堆中只有一个数据时就直接取出这个数据。
  由于堆也是用数组模拟的,i结点的父结点下标就为(i – 1) / 2,它的左右子结点下标分别为2 * i + 1和2 * i + 2。故堆化数组后,第一次将A[0]与A[n - 1]交换,再对A[0…n-2]重新恢复堆。第二次将A[0]与A[n – 2]交换,再对A[0…n - 3]重新恢复堆,重复这样的操作直到A[0]与A[1]交换。由于每次都是将最小的数据并入到后面的有序区间,故操作完成后整个数组就有序了。有点类似于直接选择排序。

  算法分析:堆排序的时间,主要由建立初始堆和反复重建堆这两部分的时间开销构成,平均和最坏情况下时间复杂度都是O(N*logN) ,由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件。堆排序是就地排序,辅助空间为O(1),它是不稳定的排序方法。

计数排序

  计数排序使用一个额外的数组C,其中第i个元素是待排序数组A中值等于i的元素的个数。然后根据数组C来将A中的元素排到正确的位置。

桶排序

算法思想:将数组分到有限数量的桶子里,每个桶子再分别排序,最后合并。

  假设有一组长度为N的待排关键字序列K[1….n]。首先将这个序列划分成M个的子区间(桶) 。然后基于某种映射函数 ,将待排序列的关键字k映射到第i个桶中(即桶数组B的下标 i) ,那么该关键字k就作为B[i]中的元素(每个桶B[i]都是一组大小为N/M的序列)。接着对每个桶B[i]中的所有元素进行比较排序(可以使用快排)。然后依次枚举输出B[0]….B[M]中的全部内容即是一个有序序列。
  桶排序利用函数的映射关系,减少了计划所有的比较操作,是一种Hash的思想,可以用在海量数据处理中。

  算法分析:如果输入数据有N个,分到M个桶里,时间复杂度O(M+N)。

基数排序

基本思想: Radix sort是一种非比较型整数排序算法,其原理是将整数按位数切割成不同的数字,然后按每个位数分别比较。

  实现:将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零。然后,从最低位开始,依次进行一次排序。这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列。

  算法分析:由于整数也可以表达字符串(比如名字或日期)和特定格式的浮点数,所以基数排序也不是只能使用于整数。基数排序也可以看成是对每一种基数位数使用桶排序。

排序方法的选择

(1)若n较小(如n≤50),可采用直接插入或直接选择排序。
  当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。

(2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜。

(3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。
  快速排序是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短。
  堆排序所需的辅助空间少于快速排序,并且不会出现快速排序可能出现的最坏情况。这两种排序都是不稳定的。
  若要求排序稳定,则可选用归并排序,通常可以将它和直接插入排序结合在一起使用。先利用直接插入排序求得较长的有序子文件,然后再两两归并之。因为直接插入排序是稳定的,所以改进后的归并排序仍是稳定的。

(4)若n很大,记录的关键字位数较少且可以分解时,采用基数排序较好。
  虽然桶排序对关键字的结构无要求,但它也只有在关键字是随机分布时才能使平均时间达到线性阶,否则为平方阶。同时要注意,箱、桶、基数这三种分配排序均假定了关键字若为数字时,则其值均是非负的,否则将其映射到箱(桶)号时,又要增加相应的时间。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值