递归与分治法
递归
直接或间接调用自身的算法。
分治法
将规模为n的问题分为k个规模较小的子问题。子问题和原问题相同且相互独立。递归地解决子问题并将子问题的解合并为原问题的解。
一般而言,将问题分为大小相近的子问题是最有效率的。通常将问题一分为二。
从设计模式可以看出,分治法一般用递归实现。所以分治法的效率可以通过递归表达式进行分析。则有:
其中问题规模最小为1,其时解所耗费的时间为常数单位。规模大于1时,将问题分解为k个规模为n/m的子问题。将这k个子问题的解合并耗费的时间为f(n)。则展开上式可得:
经典分治算法
二分搜索:
将数组分为两半,将中间元素和目标比较,根据结果对左边或右边递归进行二分搜索。
合并排序:
将数组分为等长的两半,对两个子数组递归进行合并排序,然后再把两个有序的子数组合并。
快速排序:
以数组中特定元素为基准,把数组分为比它大和比它小的两部分,再对两部分递归进行快速排序。
Strassen矩阵乘法:
n阶矩阵A和B相乘,可以分解为其子矩阵的乘法:
即:
然后子矩阵的乘法再分解,直到子矩阵规模为2*2.
但这种拆分没有减少矩阵乘法次数,时间复杂度和直接做矩阵乘法没有差别。故Strassen提出了新的算法:
首先算出7个矩阵:
然后有
这样只需7次子矩阵乘法就完成了矩阵相乘,算法复杂度为
最接近点对问题
分治法:
- 分解:将n个点的集合分成大小近似相等的两个子集。
- 求解:递归地求解两个子集内部的最接近点对。
- 合并(关键问题):从子空间内部最接近点对,和两个子空间之间的最接近点对中,选择最接近点对。
大整数乘法
对这个式子,我们一共要进行4次 n / 2的乘法(AC2次, AD, BC)和 3次加法,因而该算法的时间复杂度为:
T(n) = 4 * T(n / 2) + θ(n)
通过master定理可以求得 T(n) = θ(n ^ 2),跟小学算法的时间复杂度没有区别。
但是我们再来看看,我们是否可以用加法来换取乘法?因为多一个加法操作,也是常数项,对时间复杂度没有影响,如果减少一个乘法则不同。
XY=AC2^n+[(A-B)(D-C)+AC+BD]2^n/2+BD
现在的时间复杂度为:
T(n) = 3 * T(n / 2) + θ(n),通过master定理求得,T(n) = O(n^log2(3) ) = O(n^1.59 )。
棋盘覆盖
为了将这 3 个无特殊方格的子棋盘转化为特殊棋盘,我们可以用一个 L 型骨牌覆盖这 3 个较小的棋盘的汇合处,如下图所示,这 3 个子棋盘上被 L 型骨牌覆盖的方格就成为该棋盘上的特殊方格,从而将原问题化为 4 个较小规模的棋盘覆盖问题。
每次将棋盘分成四块大小相同的子棋盘,看作四个象限,判断特殊方格在哪一个象限,将四个子棋盘交界处的方格填充(除了特殊方格所在象限),然后递归对左上,右上,右下,左下进行填充(特殊方格为已填充的点)
循环赛日程表
按照分治的策略,可将所有参赛的选手分为两部分,n=2k个选手的比赛日程表就可以通过为\frac{n}{2}=2k-1个选手设计的比赛日程表来决定。递归地执行这种分割,直到只剩下2个选手时,比赛日程表的制定就变得很简单:只要让这2个选手进行比赛就可以了。
显然,这个求解过程是自底向上的迭代过程,其中左上角和左下角分别为选手1至选手4以及选手5至选手8前3天的比赛日程,据此,将左上角部分的所有数字按其对应位置抄到右下角,将左下角的所有数字按其对应位置抄到右上角,这样,就分别安排好了选手1至选手4以及选手5至选手8在后4天的比赛日程,如图©所示。具有多个选手的情况可以依此类推。
这种解法是把求解2^{k} 个选手比赛日程问题划分成依次求解2{1}、2{2}、…、2{k}个选手的比赛日程问题,换言之,2{k}个选手的比赛日程是在2^{k-1}个选手的比赛日程的基础上通过迭代的方法求得的。在每次迭代中,将问题划分为4部分:
(1)左上角:左上角为2^{k-1}个选手在前半程的比赛日程;
(2)左下角:左下角为另2{k-1}个选手在前半程的比赛日程,由左上角加2{k-1}得到,例如2{2}个选手比赛,左下角由左上角直接加2得到,2{3}个选手比赛,左下角由左上角直接加4得到;
(3)右上角:将左下角直接抄到右上角得到另2^{k-1}个选手在后半程的比赛日程;
(4)右下角:将左上角直接抄到右下角得到2^{k-1}个选手在后半程的比赛日程;