本节主要介绍分治法策略,提到了树形问题的平衡性以及基于分治策略的排序算法
本节的标题写全了就是:divide the problem instance, solve subproblems recursively, combine the results, and thereby conquer the problem
简言之就是将原问题划分成几个小问题,然后递归地解决这些小问题,最后综合它们的解得到问题的解。分治法的思想我想大家都已经很清楚了,所以我就不过多地介绍它了,下面摘录些原书中的重点内容。
1.平衡性是树形问题的关键
如果我们将子问题看做节点,将问题之间的依赖关系(dependencies or reductions)看做边,那么我们就得到了子问题图(subproblem graph ),最简单的子问题图就是树形结构问题,例如我们之前提到过的递归树的形式。也许子问题之间有依赖关系,但是对于每个子问题我们都是可以独立求解的,根据我们前面学的内容,只要我们能够找到合适的规约,我们就可以直接使用递归形式的算法将这个问题解决。[至于子问题间有重叠的话我们后面会详细介绍动态规划的方法来解决这类问题,这里我们不考虑]
前面我们学的内容已经完全足够我们理解分治法了,第3节的Divide-and-conquer recurrences,第4节的Strong induction,还有第5节的Recursive traversal
The recurrences tell you something about the performance involved, the induction gives you a tool for understanding how the algorithms work, and the recursive traversal (DFS in trees) is a raw skeleton for the algorithms.
但是,我们前面介绍Induction时总是从 n-1 到 n,这节我们要考虑平衡性,我们希望从 n/2 到 n,也就是说我们假设我们能够解决规模为原问题一半的子问题。
假设对于同一个问题,我们有下面两个解决方案,哪个方案更好些呢?
(1)T(n)=T(n-1)+T(1)+n
(2)T(n)=2T(n/2)+n
如果从时间复杂度来评价的话,前者是O(n2)的,而后者是O(nlgn)的,所以是后者更好些。下图以递归树的形式显示了两种方案的不同
2.典型的分治法
下面是典型分治法的伪代码,很容易理解对吧
# Pseudocode(ish)
def divide_and_conquer(S, divide, combine):
if len(S) == 1: return S
L, R = divide(S)
A = divide_and_conquer(L, divide, combine)
B = divide_and_conquer(R, divide, combine)
return combine(A, B)
用图形来表示如下,上面部分是分(division),下面部分是合(combination)
二分查找是最常用的采用分治策略的算法,我们经常使用的版本控制系统(evision control systems=RCSs)查找代码中发生某个变化是在哪个版本时采用的正是二分查找策略。
Python中bisect模块也正是利用了二分查找策略,其中方法bisect的作用是返回要找到元素的位置,bisect_left是其左边的那个位置,而bisect_right和bisect的作用是一样的,函数insort也是这样设计的。
from bisect import bisect
a = [0, 2, 3, 5, 6