分治法(divide&conquer)
思想: 分而治之
步骤: 先找重复性{最近重复性,最优重复性(动态规划)} 分解成子问题(结构一样规模不同所以适用递归) 最后组合每个子问题的结果
(1)将原问题分解成若干个较小的子问题(子问题与原问题结构一样,只是规模不一样)
(2)子问题又不断分解成规模更小的子问题,直到不能再分解或可以轻易计算出子问题的解
(3)利用子问题的解推导出原问题的解
需要注意:子问题之间时相互独立的
应用:
详见我的博客下面文章:
分治法之快速排序
分治法之归并排序
分治法之最大子序列问题
主定理:
解决规模为n的问题,分解为a个规模为n/b的子问题,然后在O(n^d)时间内将问题的解合并起来。
算法时间复杂度为T(n)=a*T(n/b)+O(n^d)(其中a>0,b>1,d>=0) |
---|
d>logb(a) 时间复杂度为O(n^d) |
d==logb(a) 时间复杂度为O(n^d*logn) |
d<logb(a) 时间复杂度为O(n^(logb(a))) |
比如归并排序时间复杂度为 T(n)=2T(n/2)+O(n) a=2,b=2.d=1 d==logb(a)
所以时间复杂度为O(nlogn)。关于常见递推式的时间复杂度,详见我之前写的博客:算法Day2——递归算法笔记
有些问题采用分治法策略之后,性能会有所提升是因为对于同一个操作,数据规模的范围在不断缩小。
例如对于一般常规排序如冒泡/选择/插入排序 时间复杂度为O(n^2)
使用分治策略之后 将问题分解为数据规模为n/2的两个子问题
n/2 O(n^2 /4)
n/2 O(n^2 /4)
在加上合并子问题时的遍历操作O(n)
故使用分治策略之后时间复杂度为O(n)=O(n^2 /2)+O(n)
分治算法java模板:
data type divide_conquer(problem,param 1,param2){
//1.recursion terminator 终止条件/递归基
//问题转换为可以直接容易得出结果的规模或者说达到了树中的叶子节点
if problem is None{
print_result;
return;
}
//2.prepare data 预处理数据
data=prepare_data(problem);
subproblems=split_problem(problem,data);
//3.conquer subproblems 解决当前层的子问题
subresult1=self.divide_conquer(subproblem[0],p1……)
subresult2=self.divide_conquer(subproblem[1],p1……)
……
//4.process and generate the final result组合每个子问题的结果
result=process_result(subresult1,subresult2,……)
//5.reverse the current level satates
分治比递归多了一步:在drill down和reverse statue多了一步组合子问题结果
递归算法模板参见我之前写的博客:算法Day2递归法笔记