这几天笔试了好几回了,连续碰到一个关于常见排序算法稳定性判别的问题,每每仍是多选,对于我以及和我同样拿不许的同窗可不是一个能轻易下结论的题目,固然若是你笔试以前已经记住了数据结构书上哪些是稳定的,哪些不是稳定的,作起来应该能够轻松搞定。算法
本文是针对总是记不住这个或者想真正明白到底为何是稳定或者不稳定的人准备的。 shell
首先,排序算法的稳定性你们应该都知道,通俗地讲就是能保证排序前2个相等的数其在序列的先后位置顺序和排序后它们两个的先后位置顺序相同。在简单形式化一下,若是Ai = Aj, Ai原来在位置前,排序后Ai仍是要在Aj位置前。 数组
其次,说一下稳定性的好处。排序算法若是是稳定的,那么从一个键上排序,而后再从另外一个键上排序,第一个键排序的结果能够为第二个键排序所用。基数排序就 是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。另外,若是排序算法稳定,对基于比较的排序算法而言,元素交换 的次数可能会少一些(我的感受,没有证明)。 数据结构
回到主题,如今分析一下常见的排序算法的稳定性,每一个都给出简单的理由。 ui
(1)冒泡排序 spa
冒泡排序就是把小的元素往前调或者把大的元素日后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。因此,若是两个元素相等,我想你是不会再无 聊地把他们俩交换一下的;若是两个相等的元素没有相邻,那么即便经过前面的两两交换把两个相邻起来,这时候也不会交换,因此相同元素的先后顺序并无改 变,因此冒泡排序是一种稳定排序算法。 设计
(2)选择排序 排序
选择排序是给每一个位置选择当前元素最小的,好比给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n-1个元素,第n个 元素不用选择了,由于只剩下它一个最大的元素了。那么,在一趟选择,若是当前元素比一个元素小,而该小的元素又出如今一个和当前元素相等的元素后面,那么 交换后稳定性就被破坏了。比较拗口,举个例子,序列5 8 5 2 9, 咱们知道第一遍选择第1个元素5会和2交换,那么原序列中2个5的相对先后顺序就被破坏了,因此选择排序不是一个稳定的排序算法。 递归
(3)插入排序
插入排序是在一个已经有序的小序列的基础上,一次插入一个元素。固然,刚开始这个有序的小序列只有1个元素,就是第一个元素。比较是从有序序列的末尾开 始,也就是想要插入的元素和已经有序的最大者开始比起,若是比它大则直接插入在其后面,不然一直往前找直到找到它该插入的位置。若是遇见一个和插入元素相 等的,那么插入元素把想插入的元素放在相等元素的后面。因此,相等元素的先后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,因此插入排序是稳 定的。 内存
(4)快速排序
快速排序有两个方向,左边的i下标一直往右走,当a[i] <= a[center_index],其中center_index是中枢元素的数组下标,通常取为数组第0个元素。而右边的j下标一直往左走,当a[j] > a[center_index]。若是i和j都走不动了,i <= j, 交换a[i]和a[j],重复上面的过程,直到i>j。 交换a[j]和a[center_index],完成一趟快速排序。在中枢元素和a[j]交换的时候,颇有可能把前面的元素的稳定性打乱,好比序列为 5 3 3 4 3 8 9 10 11, 如今中枢元素5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,因此快速排序是一个不稳定的排序算法,不稳定发生在中枢元素和a[j]交换的时刻。
(5)归并排序
归并排序是把序列递归地分红短序列,递归出口是短序列只有1个元素(认为直接有序)或者2个序列(1次比较和交换),而后把各个有序的段序列合并成一个有 序的长序列,不断合并直到原序列所有排好序。能够发现,在1个或2个元素时,1个元素不会交换,2个元素若是大小相等也没有人故意交换,这不会破坏稳定 性。那么,在短的有序序列合并的过程当中,稳定是是否受到破坏?没有,合并过程当中咱们能够保证若是两个当前元素相等时,咱们把处在前面的序列的元素保存在结 果序列的前面,这样就保证了稳定性。因此,归并排序也是稳定的排序算法。
(6)基数排序
基数排序是按照低位先排序,而后收集;再按照高位排序,而后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优 先级排序,最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,因此其是稳定的排序算法。
(7)希尔排序(shell)
希尔排序是按照不一样步长对元素进行插入排序,当刚开始元素很无序的时候,步长最大,因此插入排序的元素个数不多,速度很快;当元素基本有序了,步长很小, 插入排序对于有序的序列效率很高。因此,希尔排序的时间复杂度会比o(n^2)好一些。因为屡次插入排序,咱们知道一次插入排序是稳定的,不会改变相同元 素的相对顺序,但在不一样的插入排序过程当中,相同的元素可能在各自的插入排序中移动,最后其稳定性就会被打乱,因此shell排序是不稳定的。
(8)堆排序
咱们知道堆的结构是节点i的孩子为2*i和2*i+1节点,大顶堆要求父节点大于等于其2个子节点,小顶堆要求父节点小于等于其2个子节点。在一个长为n 的序列,堆排序的过程是从第n/2开始和其子节点共3个值选择最大(大顶堆)或者最小(小顶堆),这3个元素之间的选择固然不会破坏稳定性。但当为n /2-1, n/2-2, ...1这些个父节点选择元素时,就会破坏稳定性。有可能第n/2个父节点交换把后面一个元素交换过去了,而第n/2-1个父节点把后面一个相同的元素没 有交换,那么这2个相同的元素之间的稳定性就被破坏了。因此,堆排序不是稳定的排序算法
1 快速排序(QuickSort)
快速排序是一个就地排序,分而治之,大规模递归的算法。从本质上来讲,它是归并排序的就地版本。快速排序能够由下面四步组成。
(1) 若是很少于1个数据,直接返回。
(2) 通常选择序列最左边的值做为支点数据。
(3) 将序列分红2部分,一部分都大于支点数据,另一部分都小于支点数据。
(4) 对两边利用递归排序数列。
快速排序比大部分排序算法都要快。尽管咱们能够在某些特殊的状况下写出比快速排序快的算法,可是就一般状况而言,没有比它更快的了。快速排序是递归的,对于内存很是有限的机器来讲,它不是一个好的选择。
2 归并排序(MergeSort)
归并排序先分解要排序的序列,从1分红2,2分红4,依次分解,当分解到只有1个一组的时候,就能够排序这些分组,而后依次合并回原来的序列中,这样就能够排序全部数据。合并排序比堆排序稍微快一点,可是须要比堆排序多一倍的内存空间,由于它须要一个额外的数组。
3 堆排序(HeapSort)
堆排序适合于数据量很是大的场合(百万数据)。
堆排序不须要大量的递归或者多维的暂存数组。这对于数据量很是巨大的序列是合适的。好比超过数百万条记录,由于快速排序,归并排序都使用递归来设计算法,在数据量很是大的时候,可能会发生堆栈溢出错误。
堆排序会将全部的数据建成一个堆,最大的数据在堆顶,而后将堆顶数据和序列的最后一个数据交换。接下来再次重建堆,交换数据,依次下去,就能够排序全部的数据。
4 Shell排序(ShellSort)
Shell排序经过将数据分红不一样的组,先对每一组进行排序,而后再对全部的元素进行一次插入排序,以减小数据交换和移动的次数。平均效率是O(nlogn)。其中分组的合理性会对算法产生重要的影响。如今多用D.E.Knuth的分组方法。
Shell排序比冒泡排序快5倍,比插入排序大体快2倍。Shell排序比起QuickSort,MergeSort,HeapSort慢不少。可是它相对比较简单,它适合于数据量在5000如下而且速度并非特别重要的场合。它对于数据量较小的数列重复排序是很是好的。
5 插入排序(InsertSort)
插入排序经过把序列中的值插入一个已经排序好的序列中,直到该序列的结束。插入排序是对冒泡排序的改进。它比冒泡排序快2倍。通常不用在数据大于1000的场合下使用插入排序,或者重复排序超过200数据项的序列。
6 冒泡排序(BubbleSort)
冒泡排序是最慢的排序算法。在实际运用中它是效率最低的算法。它经过一趟又一趟地比较数组中的每个元素,使较大的数据下沉,较小的数据上升。它是O(n^2)的算法。
7 交换排序(ExchangeSort)和选择排序(SelectSort)
这两种排序方法都是交换方法的排序算法,效率都是 O(n2)。在实际应用中处于和冒泡排序基本相同的地位。它们只是排序算法发展的初级阶段,在实际中使用较少。
8 基数排序(RadixSort)
基数排序和一般的排序算法并不走一样的路线。它是一种比较新颖的算法,可是它只能用于 整数的排序,若是咱们要把一样的办法运用到浮点数上,咱们必须了解浮点数的存储格式,并经过特殊的方式将浮点数映射到整数上,而后再映射回去,这是很是麻 烦的事情,所以,它的使用一样也很少。并且,最重要的是,这样算法也须要较多的存储空间。
9 总结
下面是一个总的表格,大体总结了咱们常见的全部的排序算法的特色。
排序法 | 平均时间 | 最差情形 | 稳定度 | 额外空间 | 备注 |
冒泡 | O(n2) | O(n2) | 稳定 | O(1) | n小时较好 |
交换 | O(n2) | O(n2) | 不稳定 | O(1) | n小时较好 |
选择 | O(n2) | O(n2) | 不稳定 | O(1) | n小时较好 |
插入 | O(n2) | O(n2) | 稳定 | O(1) | 大部分已排序时较好 |
基数 | O(logRB) | O(logRB) | 稳定 | O(n) | B是真数(0-9), R是基数(个十百) |
Shell | O(nlogn) | O(ns) 1<s<2 | 不稳定 | O(1) | s是所选分组 |
快速 | O(nlogn) | O(n2) | 不稳定 | O(nlogn) | n大时较好 |
归并 | O(nlogn) | O(nlogn) | 稳定 | O(1) | n大时较好 |
堆 | O(nlogn) | O(nlogn) | 不稳定 | O(1) | n大时较好 |
选择排序、快速排序、希尔排序、堆排序不是稳定的排序算法,冒泡排序、插入排序、归并排序和基数排序是稳定的排序算法。 |