layout: posttitle: 数据结构与算法—快速排序
date: 2019-10-10
tag: 数据结构与算法
分而治之(divide and conquer, D&C)
# 计算列表之和
from typing import List
def sumList(arr:List[int]) -> int:
if len(arr) == 0:
return 0
if len(arr) == 1:
return arr[0]
else:
return arr[0]+sumList(arr[1:])
# 计算列表中的元素的个数
def num_of_element(li):
if len(li) == 1:
return 1
else:
return 1 + num_of_element(li[1:])
# 计算列表中的最大值
def max_num(li):
if len(li) == 2:
return max(li)
else:
return max(li[0], max_num(li[1:]))
max_num([1,2,3,5,6,7,8,9,9,11])
快速排序
import random
# 快速排序
def quickSort(li):
if len(li) < 2: # 当数组是空或者只有一个值时,不需要排序,只需返回结果即可
return li
else:
base = li[0] # 基准值
# 使用第一个为基准值,下面就 从 1 开始
less = [i for i in li[1:] if i <= base]
# 小于等于 要不然有重复的值会被过滤掉
greater = [i for i in li[1:] if i > base]
return quickSort(less) + [base] + quickSort(greater)
sample = [random.randrange(100) for i in range(5)]
print(sample)
print(quickSort(sample))
# 随机选择基值
import random
# 快速排序
def quickSort(li):
if len(li) < 2: # 当数组是空或者只有一个值时,不需要排序,只需返回结果即可
return li
else:
idx = random.randrange(0, len(li))
base = li[idx] # 随机选择基准值
less = [i for i in li[:idx] + li[idx + 1:] if i <= base]
# 随机选择基准值,索引的时候去掉基准值,小于等于确保重复的值不被过滤
greater = [i for i in li[:idx] + li[idx + 1:] if i > base]
#print((idx, less, greater))
return quickSort(less) + [base] + quickSort(greater)
sample = [1, 2, 1, 2, 3]
print((quickSort(sample)))
再谈大 O 表示
快速排序的独特之处在于,其速度取决于选择的基准值。
最糟情况和平均情况
以上面的快速排序为例,若递归时基准值总是取最小或者最大,那么它左右两边总有一边是空,一边包含全部值,如下图,这时调用栈的高度会非常大,这是最糟情况。此时该算法的复杂度是 O()
若快速排序算法选择的基准值是每次列表中中间的那一个,那么调用的栈会比较短,每次都将数组分成两半,所以不需要太多次的递归调用就能达到基线条件。此时对应的是最佳情况,时间复杂度为 O()
最佳情况也是平均情况。只要你每次都随机地选择一个数组元素作为基准值,快速排序的平均运行时间就将为 O()。快速排序是最快的排序算法之一,也是 D&C 典范。
小结
D&C 将问题逐步分解。使用 D&C 处理列表时,基线条件很可能是空数组或只包含一个元素的数组。
实现快速排序时,请随机地选择用作基准值的元素。快速排序的平均运行时间为
O(n log n)。大O表示法中的常量有时候事关重大,这就是快速排序比合并排序快的原因所在。
比较简单查找和二分查找时,常量几乎无关紧要,因为列表很长时, O(log n) 的速度比 O(n) 快得多。