常见排序算法总结和Go标准库排序源码分析.pdf
《常见排序算法总结和Go标准库排序源码分析.pdf》由会员分享,可在线阅读,更多相关《常见排序算法总结和Go标准库排序源码分析.pdf(38页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、前后排序算法是数组相关算法的基础知识之一,它们的经典思想可以用于很多算法之中。这里详细介绍和总结7种最常见排序算法,并 用G o做了实现,同时对比这几种算法的时间复杂度、空间复杂度和稳定性。后一部分 是 对G o标准库排序实现的源码阅读和分析,理解官方是如何通过将以上排序算法进行组合来提高排序性能,完成生产环境的排序实践。排序算法分类常 见 的 这7种排序算法分别是:选 择排序冒 泡排序插 入排序希 尔排序归 并排序快 速排序堆 排序我们可以根据算法特点像复杂度、是否比较元素、内外部排序等特点对它们做分类,比如上面的算法都是内部排序的。一般可以基于算法是否比较了元素,将排序分为两类:1.比较类
2、排序:通过比较来决定元素间的相对次序。由于其平均时间复杂度不能突破$O(Nlog N)$,因此也称为非线性时间比较类排序。2.非比较类排序:不通过比较来决定元素间的相对次序。它可以突破基于比较排序的时间下界,以线性时间运行,因此也称为线性时间非比较类排序。主要实现有:桶排序、计数排序和基数排序。通过这个的分类,可以先有一个基本的认识,就是比较类排序算法的平均时间复杂度较好的情况下是$O(NlogN)$(一遍 找 元 素$0(N)$,一遍找位置$O(logN)$)o注:有重复大量元素的数组,可以通过三向切分快速排序,将平均时间复杂度降 低 到$0(N)$比较类排序算法因为非比较排序有其局限性,所
3、以它们并不常用。本文将要介绍的7种算法都是比较类排序。选择排序原理:遍历数组,从中选择最小元素,将它与数组的第一个元素交换位置。继续从数组剩下的元素中选择出最小的元素,将它与数组的第二个元素交换位置。循环以上过程,直到将整个数组排序。时间复杂度分析:$0(NN2)$。选择排序大约需要$N72/2$次 比 较 和$N$次交换,它的运行时间与输入无关,这个特点使得它对一个已经排序的数组也需要很多的比较和交换操作。实现:/选 择 排 序(selection ort)package sortsFeme Selectio.Sort(arr 心t)it for i:=O i /c八();i+m/ia:=/
4、for j:=r+j /en(rr);j+if arirj arrmin tvu八=jtmp:=arriarri=arrminarrmm=tmp)return arr)冒泡排序原理:遍历数组,比较并将大的元素与下一个元素交换位置,在一轮的循环之后,可以让未排序i的最大元素排列到数组右侧。在一轮循环中,如果没有发生元素位置交换,那么说明数组已经是有序的,此时退出排序。时间复杂度分析:$0(NA2)$实现:/冒 泡 排 序 四 sort)package sortsFeme bubb(eSort(arr(swapped:=truefor swapped swapped=fakefor i:=O i
5、;i+if arri-hl arri arr/+-l?arri=a rrli,arr7+2jswapped-tnxe)1)return arr)插入排序原理:数组先看成两部分,排序序列和未排序序列。排序序列从第一个元素开始,该元素可以认为已经被排序。遍历数组,每次将扫描到的元素与之前的元素相比较,插入到有序序列的适当位置。时间复杂度分析:插入排序的时间复杂度取决于数组的排序序列,如果数组已经部分有序了,那么未排序元素较少,需要的插入次数也就较少,时间复杂度较低。平均情况下插入排序需要$N 72/4$次 比 较 以 及$N 72/4$次交换;最坏的情况下需要$N 72/2$比 较 以 及$NA2
6、/2$次交换,最坏的情况是数组都是未排序序列(倒序)的;最好的情况下需要$N-l$次 比 较 和 0 次交换,最好的情况就是数组已经是排序序列。实现:/插入排序。八scrti。八 sort)package sortsFeme 伍sertio八Sort 口/八t),八t(for current!tadex:=X;currentlHex O&arriterator-l=temporary;iterator-arriterator=arrliterator-larriterator=temporary1return arv希尔排序原理:希尔排序,也称递减增量排序算法,实质是插入排序的优化(分组插入排
7、序)。对于大规模的数组,插入排序很慢,因为它只能交换相邻的元素位置,每次只能将未排序序列数量减少1。希尔排序的出现就是为了解决插入排序的这种局限性,通过交换不相邻的元素位置,使每次可以将未排序序列的减少数量变多。希尔排序使用插入排序对间隔d的序列进行排序。通过不断减小d,最后令d=l,就可以使得整个数组是有序的。时间复杂度:$O(dN*M)$,M表示已排序序列长度,d表示间隔,即N的若干倍乘于递增序列的长度S|8 3 S S ,s7|6 1|3()7()()o S O实现:/希尔排序(shell sort)package sortsFeme ShellSort(arr it)for d/h/2
8、);d O;d/=2 for i:=d;i=d&arrj-d arrfj;j-=d arrf/,arrfj-d=arrj-d,arrj)return arr归并排序原理:将数组分成两个子数组,分别进行排序,然后再将它们归并起来(自上而下)。具体算法描述:先考虑合并两个有序数组,基本思路是比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。再考虑递归分解,基本思路是将数组分解成好t 和 的 向,如果这两个数组内部数据是有序的,那么就可以用上面合并数组的方法将这两个数组合并排序。如何让这两个数组内部是有序的?可
9、以二分,直至分解出的小组只含有一个元素时为止,此时认为该小组内部已有序。然后合并排序相邻二个小组即可。归并算法是分治法的一个典型应用,所以它有两种实现方法:1.自上而下的递归:每次将数组对半分成两个子数组再归并(分治)2.自下而上的迭代:先归并子数组,然后成对归并得到的子数组时间复杂度分析:$O(Nlog N)$实现:/归并排序(Merge sort)pckge sortsF eme ierge(a 口,八 七 b Qmt)Jint(var r=H A ke(Ji八七怙 八()+/。八(匕)vav i -Ovar j Ofor i /。八()&j leia(b)if犯 =如J ri+j=血i+
10、eke(巾包=如Jj+)for i lei(a)“切=w什 十for j ri+j=bjj+return r/Mergesort 合并两个数组 fimc Mergcsort(iteMS mt)iiat if/c八(itehs)2.return/terns)var Middle=/2var a=McYgesort(iCMS:Middle)var b=Mergesort(itei,siddle:)return merged,b)快速排序原理:快速排序也是分治法的一个应用,先随机拿到一个基准pivot,通过一趟排序将数组分成两个独立的数组,左子数组小于或等于pivot,右子数组大于等于 pivoto
11、然后可在对这两个子数组递归继续以上排序,最后使整个数组有序。具体算法描述:1.从数组中挑选一个切分元素,称为“基 准(pivot)2.排序数组,把所有比基准值小的元素排到基准前面,所有比基准值大的元素排到基准后面(相同元素不对位置做要求)。这个排序完成后,基准就排在数组的中间位置。这个排序过程称为“分 区 (partition)3.递归地把小于基准值元素的子数组和大于基准值的子数组排序空间复杂度分析:快速排序是原地排序,不需要辅助数据,但是递归调用需要辅助栈,最好情况下是递归$log 2N$次,所以空间复杂度为$O(log2N)$,最坏情况下是递归$N-1$次,所以空间复杂度是$0(N)$。时
12、间复杂度分析:最好的情况是每次基准都正好将数组对半分,这样递归调用最少,时间复杂度为$0(N log N)$最坏的情况是每次分区过程,基准都是从最小元素开始,对应时间复杂度为$0(NAA2)$算法改进:1.分区过程中更合理地选择基准(pivot)。直接选择分区的第一个或最后一个元素做pivot是不合适的,对于已经排好序,或者接近排好序的情况,会进入最差情况,时间复杂度为$0(NA2)$2.因为快速排序在小数组中也会递归调用自己,对于小数组,插入排序比快速排序的性能更好,因此在小数组中可以切换到插入排序3.更快地分区(三向切分快速排序):对于有大量重复元素的数组,可以将数组切分为三部分,分别对应
13、小于pivot、等 于p iv o t和大于p iv o t切分元素实现:/;向 切 分 快 速 排 序:(quick sort)package sortsim port(niath/raiadn)fuiac QuickSort(arr 口/认 亡)口 讥 士(i f加 八rr)=2 return avv)pivot:=arrraiad.hat(le(arr)lowPart:=O,/en(rr)highPart:=kvake(in.tj O _,/e八()widdlePart:=O,te(rr)for-/tern:=range tarr switch case itew pivot:higkP
14、aH=appe八d(highPart/tern)lowPart=QuickSort(lowPart)higkPart=QuickSort(kigkPart)lowPart=appcidowPart,i,iddlePart.)lowPart=appe八(lowPart,kighPart.)return lowPart堆排序原理:堆排序是利用 堆积(h e a p)这种数据结构的一种排序算法。因为堆是一个近似完全二叉树结构,满足子节点的键值或索引小于(或大于)它的父节点。具体算法描述:1.将待排序数组构建成大根堆,这个堆为初始的无序区2.将堆顶元素$R_1$与最后一个元素$R_n$交换,此时得到新
15、的无序区($R_l,R_2,.R_n-l$)和 新 的 有 序 区($R _(n$),并且满足$R_l,2,.n-l)=O;i-k.MaxHeapify(i)return h)fuiac(h kvxaxHeap)MaxHeapify(i mt)L r:=2*i+1,2*i+2max:=iif I hslicevax mx=Iif r hsliceax max=r)if/=i hslicci,kslicei.ax=h.s/cemax hsliceik.MaxHcapify(iax)Feme(h size。iit(return h.heapSize)func HeapSort(slice m t)
16、in t h:=buildMaxbleap(stice)for i:=lei(h.slice)-1;r=1;i-kslice。,kslicei=h.slicei,ksliccOh.heapSize-h.MaxHeapify(O)return h.slice算法复杂度比较下面是各排序算法的复杂度和稳定性比较:注:排序算法时间复杂度(平均)时间复杂度(最好)时 间 复 杂 度(最坏)空间复杂度稳定性备注选择排序$0(NA2)$0(NA2)$0(NA2)$0(1)$o不定冒泡排序$0(NA2)$0(N)$0(NA2)$0(1)$稳定插入排序$0(NA2)$0(N)$0(NA2)$0(1)$稳定时间复
17、杂度和序有关希尔排序$0(NA13)$0(N)$0(NA2)$0(1)$稳不定改进版插入排归并排序$0(N log N)$0(N log N)$0(N log N)$0(N)$稳定扪速树序$0(N log N)$0(N log N)$0(NA2)$0(N logN)$不稳定堆排序$0(N log N)$0(N log N)$0(N log N)$0(1)$不定无法利用局部 稳定:如 果a原 本 在b前面,而a=b,排 序 之 后a仍 然 在b的前面。不稳定:如 果a原 本 在b的前面,而a=b,排 序 之 后a可能会出现在b的后面。对比这里排序的时间复杂度,归并排序、快速排序和堆排序的平均时间
18、复杂度都是$O(Nlog N)$。但是再比较最坏的情况,可以看到堆排序的下界也是$0(Nlog N)$,而快排最坏的时间复杂度是$0(N、2)$。你可能会问,按分析结果来说,堆排序应该是实际使用的更好选择,但为什么业界的排序实现更多是快速排序?实际上在算法分析中,大$0$的作用是给出一个规模的下界,而不是增长数量的下界。因此,算法复杂度一样只是说明随着数据量的增加,算法时间代价增长的趋势相同,并不是执行的时间就一样,这里面有很多常量参数的差别,比如在公式里各个排序算法的前面都省略了一个$c$,这个$c$对于堆排序来说是100,可能对于快速排序来说就是1 0,但因为是常数级所以不影响大$0$。这
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 常见 排序 算法 总结 Go 标准 源码 分析
限制150内