什么是排序稳定性?
通俗地讲就是在排序前A的位置是 i ,B的位置是 j,此时 i < j,则如果在排序后A的位置还是在B之前,那么称它是稳定的。
它的好处是,如果排序算法是稳定的,那么第一个排序结果可以为另一个排序所用。比如基数排序,先按低位排序,逐次按高位排序,低位相同的元素其顺序在高位也相同时是不会改变的。
首先给出一张在前一个博客“时间复杂度入门理解”中出现过的一副图,给出相关排序的稳定性:
接下来,将解释图中某些排序是不稳定的的原因。
I)为什么希尔排序不稳定?
A:首先要知道 Shell 排序是基于插入排序的优化排序,插入排序每次本身只能插入一位数据,希尔排序按照步长对多个元素进行插入排序。我们知道一次插入排序是稳定的,但是同时对多组数据进行插入排序,很明显就稳定了,举例:
有这样一组数:[ 13 14 94 33 82 25 59 94 65 23 45 27 73 25 39 10 ]
以第一轮是步长为5的希尔排序为例:
Before Shell Sort:
After Shell Sort:
很明显,之前 94 相对于 33 的位置就变了,这就是同时进行多次插入排序所导致的不稳定性。
II)为什么快速排序不稳定?
A:快排的思想是先设一个中枢元素(也称是基准数,对照用),对两遍进行排列,需要满足左边的元素都比中枢元素小,右边元素都比中枢元素大,随后对左和右的子序列也进行这样的操作(分治)。而该排序不稳定的关键就是中枢元素在调换到序列中间的时候,会破坏了原来位于中央的元素的稳定性,比如有一个序列:
6 1 2 7 9 3 4 5 10 8 ,我们定义 6 为中枢元素
进行第一轮快排后,得3 1 2 5 4 6 9 7 10 8
这样操作就会把 3 的稳定性破坏。所以快排是一个不稳定的排序算法,不稳定发生在中枢元素的交换时刻。
III)为什么选择排序是不稳定的?
A:选择排序的操作是这样:先在未排序的序列中选择最小的元素(或最大的元素),把它放入第一个位置,再在剩余未排序序列中选择第二小的,放在第二个位置...以此类推,直到所有序列排序完毕。但是,这样直接让最小元素与第一个位置上的元素进行交换,会破坏第一个元素的稳定性。
举个例子:6 2 1 7 9 3 4 5 10 8
在6 和 1 的交换同时,6 与 2的稳定性就被破坏了。
IV)为什么堆排序是不稳定的?
A:堆的结构是第 i 个结点的左子结点为 2i ,右子结点为2i + 1,子结点 i 的父结点的位置在 floor( (i-1)/2 ),最大堆要求父结点要大于它的两个子结点,最小堆要求父结点要小于它的两个子结点。而堆排序就是移除位于第一个数据的根结点,并做做大堆调整的递归运算。
在一个长为n的序列中,首先从第 n/2 个结点和其子结点一共三个结点开始选择是最大堆还是最小堆,这三个元素的选择当然并不会影响稳定性。但是之后第n/2 -1 ,n/2 -2, ... 2, 1 个结点开始选择就会影响稳定性了。因为有可能第 n/2 -2 个父结点把其后的一个元素交换了,而第 n/2 -3 个父结点的后一个结点却没有被交换,那么它们之间的稳定性就被破坏了。
注意
排序算法是否为稳定的是由具体算法决定的,不稳定的算法在某种条件下可以变为稳定的算法,而稳定的算法在某种条件下也可以变为不稳定的算法。比如冒泡,若让交换的条件改成 r[j] >= r[j+1],两个相等的记录就会交换位置,从而变成不稳定的算法。