一、关于常见排序算法稳定性的问题
排序算法的稳定性,简单地讲就是能保证排序前2个相等的数其在序列的前后位置顺序和排序后它们两个的前后位置顺序相同,在简单形式化一下,如果A1 = A2,序列中A1在A2位置前,排序后A1还是要在A2位置前,那么这个算法就算稳定。
(1)冒泡排序
冒泡排序就是把小的元素往前调或者把大的元素往后沉。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,没必要再进行交换。如果两个相等的元素没有相邻,那么通过前面的两两交换这两个元素也会相邻,仍然不需要交换。所以相同元素的前后顺序并没有改变,冒泡排序是稳定的排序算法。
(2)选择排序
选择排序是给每个位置选择当前元素最小的,比如给第一个位置选择最小的,在剩余元素里面给第二个元素选择第二小的,依次类推,直到第n - 1个元素,第n个元素不用选择,因为剩最后一个元素,说明它是最大的元素了。那么,在一趟选择,如果当前元素比某个元素大,而某个元素又出现在一个和当前元素相等的元素后面,那么交换后稳定性就被破坏了。比如:序列5 8 5 2 9,当选择第一个5去和剩余元素比较时,5会和2交换,那么原序列中2个5的相对前后顺序就被破坏了,说明选择排序不是一个稳定的排序算法。
(3)直接插入排序 插入排序是将一个序列分为已排序部分和未排序部分,从未排序部分获取一个关键字作为待排序数,从已排序部分找到合适的位置,插入这个数据。如果碰见一个和插入元素相等的,那么被插入元素放在相等元素的后面。所以,相等的两个元素前后顺序没有改变,所以插入排序是稳定的。
(4)快速排序 在序列中选一个基准,比这个数据小的放在它左边,比它大的放在右边,再继续选择基准进行比较。从0下标的数据开始作为基准,0下标则会空出来,此时从最右边进行比较,左右以此类推。在交换过程中很有可能把前面的元素的稳定性打乱,比如序列为5 3 3 4 3 8 9 10 11,现在5和3(第5个元素,下标从1开始计)交换就会把元素3的稳定性打乱,所以快速排序是一个不稳定的排序算法,不稳定发生在元素交换的时候。
(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个相同的元素之间的稳定性就被破坏了。所以,堆排序不是稳定的排序算法。
结论:选择排序、快速排序、希尔排序、堆排序等算法不稳定;冒泡排序、直接插入排序、归并排序和基数排序等算法稳定。