在分析分治算法的时间复杂度时,我们常常会碰到如下形式的递推式:
T(n)=aT(bn)+f(n)
主定理(Master Theorem) 为我们提供了一个强大的工具,可以机械化地求解这类递推式。不过,与其死记公式,不如从头建立起直觉。
一个具体例子
我们先从经典的 归并排序(Merge Sort) 开始:
- 分解(Divide):把一个包含 n 个元素的数组分成两半(每半都是 2n)
- 解决(Conquer):递归地排序左右两半
- 合并(Combine):用 O(n) 的时间将两个有序数组合并
递推式为:
T(n)=2T(2n)+n
其基本情形为 T(1)=Θ(1)。
手动展开递归树
我们手动展开这个递推式,观察其中的规律:
- 第 0 层:T(n)=n+2T(2n)
- 第 1 层:2T(2n)=2[2n+2T(4n)]=n+4T(4n)
- 第 2 层:4T(4n)=4[4n+2T(8n)]=n+8T(8n)
继续这样展开下去:
T(n)=n+n+n+⋯+2kT(2kn)
当 2kn=1 时,递归停止,也就是 k=log2n。
在这一层,我们有 2log2n=n 个子问题,每个子问题的代价都是 Θ(1)。
每一层的总代价:
- 第 0 层:n
- 第 1 层:2⋅2n=n
- 第 2 层:4⋅4n=n
- …
- 第 i 层:2i⋅2in=n
- …
- 第 log2n 层:n⋅1=n(基本情形)
总成本为:
T(n)=log2n+1 层n+n+⋯+n=n(log2n+1)=Θ(nlogn)
这就是我们熟悉的归并排序复杂度。
推广这个规律
现在我们把它推广。考虑如下递推式:
T(n)=aT(bn)+f(n)
其中:
- a≥1 表示子问题的个数
- b>1 表示问题规模缩小的倍数
- f(n) 表示分解与合并的代价
构造递归树:
- 第 0 层:f(n)
- 第 1 层:a⋅f(bn)
- 第 2 层:a2⋅f(b2n)
- 第 i 层:ai⋅f(bin)
这棵树的深度是 logbn,在叶子层我们有 alogbn 个规模为 Θ(1) 的子问题。
而且:alogbn=(blogba)logbn=blogba⋅logbn=nlogba。
总成本为:
T(n)=i=0∑logbn−1aif(bin)+Θ(nlogba)
T(n) 的渐近行为取决于 f(n) 与 nlogba 的比较关系。
主定理
主定理(Master Theorem):设 T(n)=aT(n/b)+f(n),其中 a≥1,b>1,并且 f(n) 渐近为正。令 c=logba。
情况 1: 如果 f(n)=O(nc−ϵ),其中某个 ϵ>0,那么 T(n)=Θ(nc)=Θ(nlogba)
情况 2: 如果 f(n)=Θ(nc),那么 T(n)=Θ(nclogn)=Θ(nlogbalogn)
情况 3: 如果 f(n)=Ω(nc+ϵ),其中某个 ϵ>0,并且对充分大的 n 有 af(n/b)≤kf(n),其中 k<1(正规性条件),那么 T(n)=Θ(f(n))
理解这三种情况
情况 1:叶子主导
例子:T(n)=4T(n/2)+n
这里 a=4,b=2,因此 c=log24=2。
我们有 f(n)=n=O(n2−ϵ),其中 ϵ=1。
展开验证:
T(n)=n+4⋅2n+16⋅4n+⋯+4log2n⋅Θ(1)=n+2n+4n+⋯+n2⋅Θ(1)=n(1+2+4+⋯+2log2n−1)+Θ(n2)=n⋅Θ(n)+Θ(n2)=Θ(n2)
叶子层贡献了 Θ(n2),它压过了根节点处的 Θ(n) 工作量。
情况 2:均衡
例子:T(n)=4T(n/2)+n2
这里 c=2,并且 f(n)=n2=Θ(n2)。
展开验证:
第 i 层的贡献为:4i⋅(2in)2=4i⋅4in2=n2
一共有 log2n 层,每层都贡献 n2。
T(n)=log2n 层n2+n2+⋯+n2=Θ(n2logn)
情况 3:根主导
例子:T(n)=4T(n/2)+n3
这里 c=2,并且 f(n)=n3=Ω(n2+ϵ),其中 ϵ=1。
检查正规性条件:
af(n/b)=4⋅(2n)3=4⋅8n3=2n3=21f(n)
因此 k=21<1,满足条件。
验证:
- 第 0 层:n3
- 第 1 层:4⋅(n/2)3=n3/2
- 第 2 层:16⋅(n/4)3=n3/4
总和:
T(n)=n3+2n3+4n3+⋯=n3i=0∑∞2i1=2n3=Θ(n3)
因为这是一个收敛的几何级数,所以根节点的成本占主导。
Akra-Bazzi 方法
主定理是有局限的。它不能用于以下情况:
- 非常数的 a 或 b
- 带对数因子的 f(n)
- 递推式中存在减法
Akra-Bazzi 定理
Akra-Bazzi 定理:设 T(x) 满足:
T(x)=g(x)+i=1∑kaiT(bix+hi(x))
对于 x≥x0,其中:
- ai>0 且 0<bi<1 是常数
- ∣hi(x)∣=O(x/log2x)(小扰动项)
- g(x) 是多项式有界的
令 p 是满足下式的唯一实数:
i=1∑kaibip=1
那么:
T(x)=Θ(xp(1+∫1xup+1g(u)du))
例子:Strassen 算法
Strassen 矩阵乘法的递推式为:
T(n)=7T(n/2)+Θ(n2)
根据主定理(情况 1):c=log27≈2.81,并且 f(n)=O(n2.81−0.81)。
T(n)=Θ(nlog27)≈Θ(n2.81)
这比朴素的 O(n3) 更快!
例子:主定理不适用时
考虑:
T(n)=2T(n/2)+lognn
这里 f(n)=n/logn=o(n),但对任意 ϵ>0,它都不是 O(n1−ϵ)。
使用 Akra-Bazzi:a1=2,b1=1/2,因此 2⋅(1/2)p=1,解得 p=1。
∫1nu2u/logudu=∫1nulogu1du=loglogn
因此:
T(n)=Θ(n(1+loglogn))=Θ(nloglogn)
变体与扩展
上取整与下取整
主定理同样适用于:
T(n)=aT(⌈n/b⌉)+f(n)
T(n)=aT(⌊n/b⌋)+f(n)
渐近界不会改变,因为上取整 / 下取整只会引入 O(1) 的扰动。
代入法验证
请务必用代入法验证你的主定理结论。
例子:验证 T(n)=2T(n/2)+n=Θ(nlogn)。
上界猜测:T(n)≤cnlogn
T(n)=2T(n/2)+n≤2⋅c⋅2n⋅log2n+n=cn(logn−1)+n=cnlogn−cn+n≤cnlogn,当 c≥1
下界同理,可以证明 T(n)≥cnlogn。
迭代法
对于复杂的递推式,手动展开往往非常有启发性:
T(n)=T(n/3)+T(2n/3)+n
虽然它是不均匀划分,但递归树的深度是 log3/2n,而每一层的和都是 n。
每一层的总和都是 n。最长路径为 n→2n/3→4n/9→⋯,其深度是 log3/2n。
T(n)=Θ(nlogn)
常见陷阱
两种情况之间的“空隙”
不适用: T(n)=2T(n/2)+nlogn
这里的 f(n)=nlogn 既不比 n1 多一个多项式量级,也不比它少一个多项式量级。
解决办法: 使用递归树或 Akra-Bazzi:
T(n)=Θ(nlog2n)
忘记正规性条件
情况 3 要求 af(n/b)≤kf(n),其中 k<1。
一个情况 3 失效的例子:
T(n)=2T(n/2)+n(2+cosn)
这里的 f(n)=Ω(n1+ϵ) 在某些时候成立,但它会振荡。由于 cosn 的存在,我们无法找到一个统一的 k<1,因此正规性条件失效。
非常数的 a 或 b
不适用: T(n)=T(n/2)+T(n/4)+n
主定理要求 a 和 b 都是常数,此时应使用 Akra-Bazzi。
延伸阅读