快速排序
快速排序(Quicksort),又称划分交换排序(partition-exchange sort)。通过一趟快速排序将要排序的数据看做独立的两部分和一个确定最终位置的元素。以该元素为分割线,左侧都比该元素小,右侧都比该元素大(当遇到多个相等的值时控制放在分割线的同一侧)。然后按此方法对左右两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
- 步骤:
- 从数列中挑出一个元素,称为"基准"
- 重新排序数列,所有元素比基准值小的摆放在基准前面(左侧),所有元素比基准值大的摆在基准的后面(右侧),相同的数可以统一放到任一边。在这个分区结束之后,该基准就处于数列的中间位置。这个称为分区操作
- 递归地把小于基准值元素的子数列和大于基准值元素的子数列分别进行快速排序
递归的终止条件,是操作范围的长度为零或一,不免不断打开函数自身形成死循环。
快速排序的分析
- 对元素单次执行快速排序的代码
def quick_sort(alist):
'''快速排序'''
n = len(alist)
mid_value = alist[0] # 取分割对象存储在变量中,便于后续值的交换而不丢失
low = 0
high = n - 1
while low < high:
'''外层循环控制low与high交替移动'''
while low < high and alist[high] >= mid_value:
# 把等号放在一边处理,当有多个相等值时,保证都排列在mid_value的一侧,将mid_value视为分割线
'''high游标左移'''
high -= 1
alist[low] = alist[high]
while low < high and alist[low] < mid_value:
'''low指标右移'''
low += 1
alist[high] = alist[low]
# high - = 1
# 执行完一次快速排序后,循环退出,此时时low=high。
alist[high] = mid_value # 确认分割值的最终位置,将存储在变量中的分割值赋值给当前游标位置
'''
后续比较时将一个列表视为两部分[0,low]和[high+1,n-1]来再次执行快速排序。这就涉及到递归函数,即函数套函数
quick_sort(alist[:low])
quick_sort(alist[high + 1:])
'''
- 使用递归完整的快速排序代码
def quick_sort(alist, start, end): # start起始索引,end终止索引;提供参数,确保在递归时可以动态改变对原列表的操作范围
'''快速排序'''
if start >= end:
# 递归函数的终止条件,当整个序列可操作的范围只有一个值时完成当前部分排序。考虑low-1<start的可能,条件设为start>=end,满足则跳出当前这层函数
return
else:
mid_value = alist[start]
low = start
high = end
while low < high:
while low < high and alist[high] >= mid_value:
high -= 1
alist[low] = alist[high]
while low < high and alist[low] < mid_value:
low += 1
alist[high] = alist[low]
alist[high] = mid_value
# 递归函数的参数不能是原列表的切片,切片生成新对象,则快速排序无法对原列表生效
quick_sort(alist, start, low - 1) # 对low左边部分执行快速排序
quick_sort(alist, high + 1, end) # 对low右边部分执行快速排序
测试
if __name__ == '__main__':
li = [30, 40, 60, 10, 20, 50]
print(li)
quick_sort(li, 0, len(li) - 1)
print(li)
执行结果:
[30, 40, 60, 10, 20, 50]
[10, 20, 30, 40, 50, 60]
时间复杂度
- 最优时间复杂度:O(nlogn)
(理想情况下对半分割,每次分割成两个同等长度的部分。即n/(2^x)=1,x=log2(n)) - 最坏时间复杂度:O(n^2)
(最坏情况不分割,即每次分割只有一侧有值,则需要分n次) - 稳定性:不稳定