- D&C将分体分解处理,对于列表来说base case通常是空列表或单元素列表
- 实现快速排序时,请随机选择pivot,BigO = O(n*log(n))
- BigO表示法有时常量作用很小(简单查找O(n*n)和二分查找O(logn)),但有时作用很大(如快排和合并排序,快排优于合并排序)
1. 分治法 D&C
Divide and Conquer,DC算法是递归的
- 找出base case,条件尽量简单
- recursive:不断将问题分解(缩小规模),直到符合base case
例1. 矩形分最大方形
- base case(最简单的矩形):一条边是另一条边的整数倍(l=25,w=50),那么最大的方块是25*25,可分出两个方块
- recursive case:以小的边作为方块长度,找出最多的方块,之后剩余的那个矩形(因为不满足base case)再进行递归,实现缩小问题规模,判断是否符合基线条件
例2. 数组加和
Base case,即最简单的数组
- 空数组,总和为0
- 一个元素的数组,总和为元素本身
Recursive
- 每次取出数组中一个数
- 将结果改为该数 + 剩余数组的加和
2. 快速排序(例3)
C语言标准库中的快排是基于D&C实现的
归纳证明
归纳证明是一种证明算法有效性的方式,它分为两步:基线条件和归纳条件
举例:证明我能爬到梯子的最顶层
- 基线条件:我已经站到第一个横档上
- 递归条件:如果我能站在一个横档上,我就能将脚放到上一层横档上
结论:我能爬到最顶层
归纳证明与分治法协同发挥作用,得出了快速排序算法
快排的实现
Base case
- 最简单的数组,即根本不需要排序的数组:空数组或就含有一个元素
Recursive case
- 找到一个pivot(可以是数组中的一个数),将比pivot小的数归纳为一个数组放到左侧,将比pivot大的数归为一个数组放到右侧
- 分别对左侧和右侧的数组进行快排
- 如果新生成的数组满足base case则停止
def quick_sort(arr):
if len(arr) < 2:
return arr
else:
pivot = arr[0]
less_arr = [x for x in arr if x < pivot]
greater_arr = [x for x in arr if x > pivot]
return quick_sort(less_arr) + [pivot] + quick_sort(greater_arr)
3. BigO
快速排序前是没有检测数组是否有序,因此,如果数组有序,快排还是会重复一遍递归
如果选用第一个元素作为pivot,那么会产生快排的最糟情况,时间复杂度为O(n*n)
- 因为每次选择最小元素,调用栈深度O(n)(调用栈表示递归过程中函数体和变量存储空间,每次递归外层函数体结束释放一层栈空间)
- 每一层进行less和greater归并需要O(n)
- 因此总BigO = O(n) * O(n)
最优情况(也是平均情况)
- 栈深度为O(logn),因为每次选择的元素是中间值(中位数为理想情况,相当于二分法 -> log(n))
- 每一层归纳 O(n)
- BigO = O(n*log(n))
快排和合并排序
- 其本质上都是O(n*log(n))
- 但是快排的常量更小,所以快排的速度更快