要想弄清楚快速排序为什么不稳定,我们先来看一下快速排序的步骤:
步骤:
1:选取基准数字;
2:将基准数字放在列表头部(尾部);
3:开始遍历,如果数字比基准大,则位置不变,如果比基准小,则将该值与前面第一个比基准大的值交换位置(此处就会引起相同值的相对位置的变换,即排序不稳定),如果前面没有比基准大的数字就不用替换了;完成一边遍历后;
4:交换头部的基准数字和最后一个比基准数字小的数字的位置,可以保证基准数字前面的值都小,后面的都大。在此交换过程中也会出现排序的不稳定。
5:对基准数字前面和后面序列重复1-4步骤,最终完成排序。
备注:
a:如果选取基准数字是随机选取,也会导致出现排序的不稳定,如果每次选用头部(尾部)数字,则导致排序不稳定的点就是其他两个;
b:这种严格的过程除了递归要使用辅助栈来存储递归函数的索引外,不需要使用任何额外空间,空间复杂度为递归的深度logn,最坏的情况为n,即每次基准数字就是最小的值或者最大值。
c:网上的大部分此类问题都只是片面的说了上面3处不稳定原因中的一点,有点误人子弟,而且百度百科中的快速排序python版本代码,在递归中借助了其他辅助空间去实现的,但是本文上述的严格的快速排序除了递归是不能借用其他辅助空间的。
d:本文中在描述中是将基准数字放在列表头部的。
下面时快速排序动图,可以辅助理解:链接在这,https://zhuanlan.zhihu.com/p/129781218
但是但是,重点来了,如果使用额外的辅助空间,就可以实现稳定的快速排序:代码如下:
- def MySort(self , arr ):
- if len(arr) <= 1:
- return arr
- left, right = [], []
- for i in range(1,len(arr)):
- if arr[i] < arr[0]:
- left.append(arr[i])
- else:
- right.append(arr[i])
return self.MySort(left) + [arr[0]] + self.MySort(right)
我们每次选取头部元素作为基准数字arr[0],在递归中定义左右列表(left,right)分别存储两种数字,并且让小于基准数字的放在左边,大于等于的放在右边(必须是大于等于),这样就保证了相同数字的相对位置,因为我们选的是头部数字,它原本就在与它相同数字的左边,并且我们用大于等于限制了与基准数字相同的数字永远在基准数字的右侧,在返回时,使用self.MySort(left) + [arr[0]] + self.MySort(right) 也进一步保证相同数字的相对位置。
总结:
1:导致快速排序不稳定的因素(除了递归不适用其他额外空间):
a:选取基准数字时随机选择;
b:在遍历过程中,将小于基准的数字与第一个大于基准的数字位置交换;
c:在每次遍历完后,将头部的基准数字与最后一个小于基准的数字交换位置。
2:使用额外辅助空间可以在时间复杂度不变的情况下使得快速排序变得稳定,此时空间复杂度会变为O(n+logn).