算法导论 第四章 分治策略
#define lg log_2
Q : 分治策略中如何求解一个问题 ?
递归求解
每层递归时 , 需要三个步骤 : 分解 , 解决和合并
当问题足够大需要递归求解 , 那么为递归情况
当问题足够小时 , 称为基本情况
Q : 如何求解一个问题的递归式 ?
- 代入法 . 猜测一个界 , 然后用数学归纳法证明
- 递归树法 . 将递归式转换为一棵树 , 节点表示不同层次递归调用代价 , 利用边界和技术求解递归式
- 主方法 : 求解形如\(T(n) = a\times T(\frac{n}{b} + f(n)\)的界
该公式描述这么一种分治 : 生成\(a\)个子问题 , 每个问题的复杂度为原问题的\(\frac{1}{b}\) , 分解合并花费的时间为\(f(n)\)
4.1 最大子数组问题
问题 : 给定一个数组\(a\) , 求最大的差值\(a_j-a_i ,j\ge i\)
思路 :
观察到是求解差值 , 所以实际上是求变换量 , 那么考虑差分
设\(b_i = a_i-a_{i-1}\) , 那么就是求一对\(l,r\)使得\(\sum_{i=l}{i\le r}b_i\)最大
即求\(b\)数组的一个最大和的子数组
(我知道的解法是贪心 , 看看书上咋写的)
考虑用分治策略求解 , 从\(mid\)劈开 , 那么所求的\(l,r\)情况无非 :
- 都在左边
- 都在右边
- 左右一半
1和2都好考虑 , 考虑3. 3实际上有一部分为\(l,mid\) , 另一部分为\(mid+1 , r\)
FIND-MAX-CROSSING-SUBARRAY(A,low,mid,high)
left-sum = -inf
sum = 0
for i = mid downto low
sum = sum + A[i]
if sum > left-sum
left-sum = sum
max-left = i
right-sum = -inf
sum = 0
for i = mid+1 to high
sum = sum + A[i]
if sum > right-sum
right-sum = sum
max-right = j
return (max-left,max-right,left-sum+right-sum)
进而 , 我们可以设计求解该问题的代码
FIND-MAXIUM-SUBARRAY(A,low,high)
if high == low
return (low,high,A[low])
else mid = (low + high)/2
(left-low,left-high,left-sum) = FIND-MAXIUM-SUBARRAY(A,low,mid)
(right-low,right-high,right-sum) = FIND-MAXIUM-SUBARRAY(A,mid+1,high)
(cross-low,cross-high,cross-sum) = FIND-MAX-CORSSING-SUBARRAY(A,low,mid,high)
if left-sum >= right-sum and left-sum>=cross-sum
return(left-low,left-high,left-sum)
else if right-sum>=left-sum and right-sum >= cross-sum
return(right-low,right-high,right-sum)
else return (cross-low,cross-high,cross-sum)
分析一下复杂度 :
基本情况时\(T(1) = O(1)\)
\(n \lt 1\)时递归 , 每次的分解部分 , 拆成了两个\(T(\frac{n}{2})\)
而FIND-MAX-CROSSING-SUBARRAY花费\(O(n)\)
\(T(n) = 2 \times T(\frac{n}{2}) + O(n)\)
和归并排序很像 , 复杂度为\(O(nlogn)\)
4.1 练习
-
当A所有元素都是负数时 , FIND-MAXIMUM-SUBARRAY返回什么 ?
绝对值最小的负数 , 以及它的下标重复两次 -
对最大子数组问题 , 写一个暴力算法 , 复杂度为\(O(n^2)\)
BL(A,l,r)
//A为前缀和
ans = -inf
for i = l to r-1
for j = i+1 to r
ans = max(ans,A[j]-A[i])
return ans
//i ,j懒得返回
-
假设修改最大子数组的定义 , 使得可以为空数组 , 和为\(0\) , 如何修改算法 ?
加上判断 , 如果结果为负数 , 那么返回0 -
使用如下思想为最大子数组问题设计一个非递归 , 线性算法 :
设已经知道\(A[1..j]\)的最大子数组 , 那么\(A[1..j+1]\)的最大子数组只有两种可能 : -
仍为\(A[1..j]\)的最大子数组
-
左右端点为\(i\),\(j+1\)的最大子数组
FIND-MAX-SUBARRAY_LINEAR(A)
ans = -inf
cur = -inf
for i = 1 to A.size
if cur >= 0
cur = cur + A[i]
else
cur = A[i]
ans = max(ans,cur)
return ans
//abababa amazing.
4.2 矩阵乘法的Strassen算法
先给出一个朴素的\(n\times n\)矩阵乘法
SQUARE-MATRIX-MULTIPLY(A,B)
n =A.rows
let C be a new n * n matrix
for i = 1 to n
for j = 1 to n
cij = 0
for k = 1 to n
cij = cij + aik * bkj
显然是一个\(O(n^3)\)的算法
非常让人震惊的是 , 居然可以有比这更快的算法 ? ! !
本节学习Strassen的\(n\times n\)递归算法 , 时间复杂度为\(O(n^{lg7})\)
一个简单的分治算法
考虑 \(C = A \cdot B\) , 都是\(n\times n\)的矩阵
\(\begin{bmatrix}
C_{11} & C_{12}\\
C_{21} & C_{22}
\end{bmatrix}
= \begin{bmatrix}
A_{11} & A_{12}\\
A_{21} & A_{22}
\end{bmatrix}
\cdot
\begin{bmatrix}
B_{11} & B_{12}\\
B_{21} & B_{22}
\end{bmatrix}
\)
这样\(C_{11} = A_{11} \cdot B_{11} + A_{12} \cdot B_{21}\)
其余以此类推
MATRIX-MUL(A,B)
n = A.rows
if n == 1
c11 = a11 * b11
else partition A,B and C as in equations(4.9)
C11 = MATRIX-MUL(A11,B11) + MATRIX-MUL(A12,B21)
C12 = MATRIX-MUL(A11,B12) + MATRIX-MUL(A12,B22)
C21 = MATRIX-MUL(A21,B11) + MATRIX-MUL(A22,B21)
C22 = MATRIX-MUL(A21,B12) + MATRIX-MUL(A22,B22)
return C
实际操作中可用下标"分解矩阵"
\(n=1\)时 , 有\(T(1) = O(1)\)
\(n\gt 1\)时 , 共\(8\)次调用函数MATRIX-MUL , 即\(8\times T(\frac{n}{2}\) , 之后有进行了\(4\)次矩阵加法 , 每次加\(\frac{n^2}{4}\)次
所以 : $T(n) =8 \times T(n/2) + O(n^2) $
如何求解这个式子的复杂度 ? 等到4.5节再说
Strassen方法
Strassen方法相比于朴素的方法差别在于 , 通过\(7\)个递归式就可以算出矩阵乘法结果
用代入法求解递归式
两步 :
- 猜测解的形式
- 用数学归纳法求出解中常数 , 并证明解正确
当归纳法作用于小的值时 , 将猜测解代入函数 , 称为"代入法"
如 \(T(n) = 2\times T(\frac{n}{2}) + n\)
长得像归并的 , 所以考虑\(O(nlogn)\)
假设\(T(n) = cnlgn\) , 那么\(T(n)\le cnlgn\)
\(T(n) \le n lg(\frac{n}{2}) + cnlg(n) -cn + n\)
需要\(c\le 1\)
如果选\(c = 1\)
然而显然 , \(T(1) = 1 \times lg(1) = 0\)不成立
这时候将起始情况扩大到\(T(2),T(3)\)即可
\(T(2) = 2\times T(1) + 2 = 4 \le 2lg2\times c , T(3)=T(1)+T(2) + 3 \le 3lg3\times c\) , 取较大的c即可
微妙的细节
考虑\(T(n) = T(n/2) + T(n/2) + 1\)
假设\(T(n) = O(n) ,T(n)\le cn\)
\(T(n) \le cn + 1\)
换种假设 , \(T(n) \le cn-d\)
\(T(n) \le cn-2d + 1 \le cn-d\)
然后找一个合适的起始\(n\)使得\(T(n) \le cn-d\)即可
换元法
如\(T(n) = 2T(\sqrt[]{n}) + lg n\)
令\(m = lg n\)
原式 : \(T(2^m) = 2 T(2^{m/2}) + lg n\)
令\(S(m) = T(2^n)\)
\(S(m) = 2 S(m/2) + lg m\)
而这个的复杂度为\(O(mlgm)\) , 此处\(m = lg n\)
所以原式的复杂度为\(O(mlgm) = O(lg(n)lg(lg(n)))\)
4.4 用递归树求解递归式
考虑\(T(n) = 3T(n/4) + O(n^2)\)
考虑递归树 , 有\(lgn\)层 , 每个节点3个分支
深度为\(i\)时 , 子问题的规模为\(n/4^i\) , 故\(n/4^i = 1\)时得到\(i = lgn\)
考虑每一层的代码 , \(i\)层节点数为\(3^i\) , 0-lgn-1层代价为\(c{(n/4^i)}^2\) , 故\(3^i \times c{(n/4^i)}^2\)
\(T(n) = cn^2 + (\frac{3}{16})^2cn^2 + ... + (\frac{3}{16})^{lgn-1} cn^2 + O(n^{lg3}) \\ = \frac{(3/16)^{lgn-1} - 1}{3/16-1}cn^2 + O(n^{lg3}) \\ = O(n^2) \)
4.5 用主方法求解递归式
对\(T(n) = aT(n/b) + f(n)\) ,其中\(a\ge 1,b\gt 1\)
朴素的理解似乎是 , 将\(f(n)\)和\(n^{log_{b}{a}}\)比较 , 如果\(n^{log_b{a}\)更高达 , 那么\(T(n) = O(n^{log_b{a}})\) , 如果\(f(n)\)更大 , 那么\(T(n) = O(f(n))\)
若大小相当 , 那么乘上一个对数因子 , \(T(n) =O(n^{log_b{a}}lg n)\)
考虑\(T(n) = 9T(n/3) + n\)
\(f(n) = n\) , 而\(n^{log_b{a}}\)为\(n^{2}\) , 所以为\(O(n^2)\)
考虑\(T(n) = T(2n/3) + 1\)
$f(n) = 1 $ , 而\(n^{log_b{a}}\)为\(n^{1.5}\) , 所以为\(O(1)\)
考虑\(T(n) = 3T(n/4) + nlgn\)
\(f(n)=1\) , 而\(n^{log_b{a}}\)为\(n^{log_4{3}}\) , 所以为\(O(nlgn)\)
但是实际上 , 要求比较时 , 比值小于\(n^{\epsilon} ,\epsilon\)任意小
考虑Strassen算法 : \(T(n) = 7T(n/2) + O(n^2)\)
\(f(n) = n^2\) , \(n^{log_b{a}} = n^log_2{7}\) , 所以\(T(n) = O(n^{lg7})\)

浙公网安备 33010602011771号