快速排序算法:python实现、时间复杂度和算法稳定性
快速排序算法的思路
默认升序排列
排列中的一个元素,如果它左边的数都比它小,右边的数都比它大,那么它的索引就是完全排序后的索引,这个元素就找到了它在排序后的正确位置。
对左右两边的子序列重复这个方法(递归过程),最后就能得到一个有序的序列。
具体步骤
- 定义三个变量,to_sort表示先安排谁的位置,默认为一个排列的第一个数字;low表示左边的游标,和to_sort相同,默认为一个排列的第一个数字;high表示右边的游标,默认为一个排列的最后一个数字。
- 对于一个排列,第一个数字就是我们要安排的数字,已经存入了to_sort变量中,实际上是一个坑,一个可以被覆盖的空位。首先从high游标开始往左走,遇到小于to_sort的数字就停止,然后把这个数字扔给low游标(这里low游标的数字会被覆盖,而且high本身的位置上由于数值已经备份给了low游标,所以形成一个可以被覆盖的坑),此时轮到low游标走,low游标向右走,遇到比to_sort大或者相等的(这里我们统一把相等的值扔到右边)就停下扔给high,然后high继续走,high和low交替向中间靠拢直到相遇,相遇的位置会形成一个可以被覆盖的坑(它的值已经在其他位置被保存),把to_sort扔到最后剩下的坑里,完成第一次排序
- 第一次排序之后,左右两边分别形成一个排列,对排列重复2过程,当最后子排列中只有一个数值的时候,就完成了排序,这是一个递归过程。
python代码实现
def quick_sort(arr, start, end):
# 递归的出口
if start >= end:
return
# 需要三个变量,分别记录要找位置的数值(默认为第一个,存在to_sort中),左边的low游标,右边的high游标
to_sort = arr[start]
# 左边游标
low = start
# 右边游标
high = end
# 当low >= high的时候,说明完成了一个元素位置的查找,跳出循环
while low < high:
# 控制右边游标的移动,如果数字比to_sort大或者相等,游标持续左移,不满足条件时,将值赋给另一个游标
while low < high and arr[high] >= to_sort:
high -= 1
arr[low] = arr[high]
# 控制左边游标的移动,如果数字比to_sort小,游标持续右移,不满足条件时,将值赋给另一个游标
while low < high and arr[low] < to_sort:
low += 1
arr[high] = arr[low]
# 剩下一个low的坑,用来放to_sort
arr[low] = to_sort
# 这个地方用递归,对左右两边的排列再次调用快排
quick_sort(arr, start, low-1)
quick_sort(arr, low+1, end)
arr1 = [20, 87, 20, 89, 20, 20, 10]
quick_sort(arr1, 0, len(arr1)-1)
print(arr1)
时间复杂度的探讨
最优时间复杂度O(nlogn)
最优的情况就是我们每个要安排的元素都可以把待排序的区间分裂成左右两个排列。
第1次排序 得到2个排列 共安排了1个元素 时间复杂度n
第2次排序 得到4个排列 共安排了2+1个元素 时间复杂度n
第3次排序 得到8个排列 共安排了4+2+1个元素 时间复杂度n
以此类推,如果要安排n个元素,需要log2(n+1)次排序,而单次排序的时间复杂度固定为n,所以时间复杂度为nlogn
最坏时间复杂度O(n2)
最坏的情况就是每次要安排的元素都位于边缘,只在to_sort的一边有元素,而另一边没有。
第1次排序 得到1个排列 共安排了1个元素 时间复杂度n
第2次排序 得到1个排列 共安排了1+1个元素 时间复杂度n
第3次排序 得到1个排列 共安排了1+1+1个元素 时间复杂度n
以此类推,如果要安排n个元素,需要n次排序,而单次排序的时间复杂度固定为n,所以时间复杂度为O(n2)
快排算法稳定性的讨论
排序算法的稳定性指的是对于值相同的两个数字,排序后会不会改变它们的相对位置,如果改变就是不稳定的。
比如考虑列表a = [18,20,20,25,10],排序后第一个20(a[1])会被扔到第二个20(a[2])的右边,那么列表中的两个20非原序,即非稳定的。