分治算法(D&C)
分治算法是一种思想,他把一个大问题缩小为规模比较小的问题,小问题得到解决之后,大问题也随之解决。分治算法通常利用递归来实现。利用递归实现的步骤如下:
- 明确递归函数的返回值;
- 寻找基线条件,这种条件务必简单;
- 实现递归体:不断将问题分解(缩小问题规模),直到符合基线条件;
例如,我们有一块土地,尺寸为 ,现要求把这块土地划分为均匀的方块且要求尽可能的大,如何进行划分呢?
我们利用分治思想来解决这个问题,分治算法的核心在于缩小问题的规模,的方块可以划分为三个方块,其中,是在现有基础之上能够划分的最大方块。图示如下所示:
我们的问题现在缩小到在方块 中寻找最大切分方块。余下的操作以此类推…。那何时停止呢?直到方块缩小到两边长成倍数关系,即可停止。最终的切分图如下所示:
利用分治算法解决数据累加
假设要用分治算法实现列表: 的累加和。我们定义一个递归函数,用于计算列表的累加和;首先要明确 D&C 的两个问题:
- 基线条件:当列表只有一个元素时,返回元素值;
- 递归体:缩小列表规模:
def addlist(arr):
if len(arr) == 1: # 定义基线条件
return arr[0]
elif len(arr) == 0:
return 0
else: # 定义递归体
return arr[0]+addlist(arr[1:])
print(addlist([1, 2, 3, 4])) # 10
利用分治算法找出列表中的最大数**
- 基线条件:列表元素只有一个;
- 递归条件:缩小问题规模,计算剩下列表中最大数;
def calListMax(arr):
if len(arr) == 0:
return 0
if len(arr) == 1: # 基线条件
return arr[0]
value = calListMax(arr[1:])
if arr[0] <= value:
return value;
else:
return arr[0];
print(calListMax([2, 9, 2, 5, 23, 62, 34])) # 62
利用分治算法实现二分查找
- 基线条件:找到标签或没有标签;
- 递归体:见代码
# 列表不作为参数,可以提高递归的执行效率
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
def binary_sort(lowIndex, hightIndex, value):
if len(arr) == 0:
return -1
if lowIndex > hightIndex:
return -1
else:
mid = (lowIndex+hightIndex)//2;
if value == arr[mid]:
return mid;
elif value < arr[mid]:
hightIndex = mid-1
else:
lowIndex = mid+1
return binary_sort(lowIndex, hightIndex, value)
print(binary_sort(0, len(arr)-1, 8)) # 7
快速排序
快速排序是一种常用的排序算法,比选择排序快得多。例如, C语言标准库中的函数 qsort
实现的就是快速排序。快速排序也使用了D&C。快速排序的思想流程如下:假设要对列表 进行排序,如下所示:
首先选择一个基准值,比基准值小的数据放在基准值的左边,比基准值大的数据放在基准值的右边,如下所示:
然后对左区列表和右区列表执行上述相同的操作。其代码如下:
def quick_sort(arr):
if len(arr) <= 1: # 基线条件
return arr[0]
else: # 递归体
pivot = array[0] # 基准值
less = [i for i in array[1:] if i <= pivot] # 小于基准值的数列
greater = [i for i in array[1:] if i > pivot] # 大于基准值的数列
return quicksort(less) + [pivot] + quicksort(greater)
快速排序的时间复杂度
快速排序的平均时间复杂度是:;在最糟糕情况下时间复杂度为:。
时间复杂度为 的情况,此时基准值取的列表的中间值:
当对一个有序数列进行快排且基准值取第一个值时,时间复杂度会是,如下图所示:
递归调用的层数如下:
总结
- D&C将问题缩小为更小规模进行解决。使用D&C处理列表时,基线条件是空数组或单元素数组;
- 实现快排时,请随机地选择基准值的元素。快排的平均运行时间为;
- 大O表示法中的常量有时候事关重大;