主定理
鉴于先前排版的简陋和内容的缺失,现重新整理一番,在重温初赛知识的同时也方便大家学习。
下面进入正文。
不论是在 CSP 初赛题还是在分治算法分析中,经常需要计算递推算法的时间复杂度。 有时画递归树求和会比较麻烦,这时
可以用主定理来秒杀。
定理内容——简化版
我们要处理一个规模为 \(n\) 的问题,通过分治得到 \(a\) 个规模为 \(\frac{n}{b}\) 的问题「假设每个子问题的规模基本一样」,\(f(n)\)
为递推以外的计算工作「分解和合并子问题的时间」,则其时间复杂度的递推式为:
如果 \(a,b\) 为常数且 \(a≥1\),\(b>1\)「\(b=1\)时递推无意义」,\(f(n)\) 为关于 \(n\) 的函数且在渐进意义上 \(f(n)>0\),且 \(T(1)=\Theta(1)\),那么:
-
若\(f(n)<\Theta(n^{\log_ba})\),则\(T(n)=\Theta(n^{\log_ba})\)
-
若\(f(n)>\Theta(n^{\log_ba})\),则\(T(n)=f(n)\)
-
若\(f(n)=\Theta(n^{\log_ba})\),则\(T(n)=\Theta(n^{\log_ba}\log n)\)
「大者为王,等大乘\(\log\)」
以上仅仅是本人为简化记忆而整理而成的,不甚严谨但很实用,若想知道严谨的表述,参见 百度百科 或《算法导论》。
「后面会附上《算法导论》中的严谨定理内容」
简单证明
使用主定理时无需考虑递归树,但证明还是要用到的。
简要分析一下:
递归层数 | 问题个数 | 问题规模 | 该层时间开销 |
---|---|---|---|
\(0\) | \(1\) | \(n\) | \(f(n)\) |
\(1\) | \(a\) | \(\frac nb\) | \(af(\frac nb)\) |
\(2\) | \(a^2\) | \(\frac{n}{b^2}\) | \(a^2f(\frac n{b^2})\) |
\(...\) | |||
\(k\) | \(a^k\) | \(\frac{n}{b^k}\) | \(a^kf(\frac n{b^k})\) |
若 \(k\) 为递归的最后一层,则:
于是:
观察 \(a\) 的指数容易发现,一共有 \(\log_bn-0+1\) 项,\(n\) 很大时近似于有 \(\log_bn\) 项
考虑其中最大的一项「不考虑其他项是因为在时间复杂度分析中忽略较小项」,进行分类讨论:
-
\(f(n)\) 最大,\(T(n)=f(n)\)
此时 \(f(n)>a^{\log_bn}f(1)\),即 \(f(n)>\Theta(n^{log_ba})\) ,是定理中的第2种情况
-
\(a^{\log_bn}f(1)\) 最大,\(T(n)=\Theta(a^{\log_bn})\)
诶,似乎与定理中第1种情况的 \(T(n)=\Theta(n^{log_ba})\) 不太一样?
其实是一样的:
首先,设 \(n^x=a\) ①,则 \(x=\log_na\) ,将 \(x\) 代入①,则有:\(n^{\log_na}=a\) ②
同时,对①的两边同时对 \(b\) 取对数,则有:\(\log_b{n^x}=x\log_bn=\log_ba\)
因此 \(x=\log_na=\frac{\log_ba}{\log_bn}\)
因此 \(\log_na\cdot\log_bn=\frac{\log_ba}{\log_bn}\cdot\log_bn=\log_ba\) ③
于是,由②、③可得:
\[a^{\log_bn}=(n^{\log_na})^{\log_bn}=n^{\log_na\cdot\log_bn}=n^{\log_ba} \]于是,
\[T(n)=\Theta(a^{\log_bn})=\Theta(n^{log_ba}) \]嘿嘿,这不就一样了吗?
-
没有最大!每一项一样大,\(f(n)=a^{\log_bn}f(1)=\Theta(a^{\log_bn})=\Theta(n^{log_ba})\)
此时就不能忽略最小的项了于是每一项都要加起来「前面讲过,一共有近似于 \(\log_bn\) 项」
于是
\[T(n)=\Theta(n^{log_ba})\cdot\log_bn=\Theta(n^{log_ba}\log_bn) \]时间复杂度中,\(n\) 的对数忽略底数,故
\[T(n)=\Theta(n^{log_ba}\log n) \]这便是定理中的第3种情况。
例题
是不是感觉很简单?
下面来练习一下吧!
-
例题 \(1\)
假设某算法的计算时间表示为递推关系式
\[T(n)=3T(\frac{n}{2})+\Theta(n),T(1)=\Theta(1) \]则算法的时间复杂度为( )
A. \(\Theta(n)\)
B. \(\Theta(n^{\log_23})\)
C. \(\Theta(n\log n)\)
D. \(\Theta(n^{\log_23}\log n)\)
分析:
显然,在这里 \(a=3,b=2,f(n)=\Theta(n)\)
于是 \(\Theta(n^{\log_ba})=\Theta(n^{\log_23})>\Theta(n^{\log_22})=\Theta(n)=f(n)\)
即 \(f(n)<\Theta(n^{\log_ba})\),为定理的第一种情况。
故 \(T(n)=\Theta(n^{\log_ba})=\Theta(n^{\log_23})\),选B。
选择题肯定难不倒你,下面给你递推关系式,你自己求时间复杂度吧~
「其实是懒得写了哈哈」
-
例题 \(2\)
「二分查找」
\(T(n)=T(\frac n2)+\Theta(1)\)
分析:
\(a=1,b=2,f(n)=\Theta(1)\)
\(\Theta(n^{\log_ba})=\Theta(n^{\log_21})=\Theta(n^0)=\Theta(1)=f(n)\)
等大,属于情况三。
\(T(n)=\Theta(n^{\log_21}\log_2n)=\Theta(\log n)\)
-
例题 \(3\)
「归并排序」
\(T(n)=2T(\frac n2)+\Theta(n)\)
分析:
\(a=2,b=2,f(n)=\Theta(n)\)
\(\Theta(n^{\log_ba})=\Theta(n^{\log_22})=\Theta(n)=f(n)\)
等大,属于情况三。
\(T(n)=\Theta(n^{\log_22}\log_2n)=\Theta(n\log n)\)
-
例题 \(4\)
「地毯填补问题【洛谷P1228】」
\(T(n)=4T(\frac n4)+\Theta(1)\)
分析:
\(a=4,b=4,f(n)=\Theta(1)\)
\(\Theta(n^{\log_ba})=\Theta(n^{\log_44})=\Theta(n)>f(n)\)
大者为王,故 \(T(n)=\Theta(n)\)
-
例题 \(5\)
「Strassen 算法【百度百科】」
\(T(n)=7T(\frac n2)+\Theta(n^2)\)
分析:
\(a=7,b=2,f(n)=\Theta(n^2)\)
\(\Theta(n^{\log_ba})=\Theta(n^{\log_27})>\Theta(n^{\log_24})=\Theta(n^2)=f(n)\)
大者为王,故 \(T(n)=\Theta(n^{\log_27})\)
注:
众所周知,普通的矩阵乘法时间复杂度为 \(\Theta(n^3)\)。
可以通过填 \(0\) 的方法使参与运算的矩阵成为 \(2^n\times2^n\) 矩阵,再将其分为 \(4\) 个 \(2^{n-1}\times2^{n-1}\) 矩阵
最后可以将原先的矩阵乘法转化为 \(8\) 个规模为原先一半的矩阵乘法,以及 \(4\) 个矩阵加法
因此有递归式:
\[T(n)= \begin{cases} 8T(\frac n2)+\Theta(n^2) &n>1 \\ \Theta(1) &n=1 \end{cases} \]用主定理计算一下:
\(a=8,b=2,f(n)=\Theta(n^2)\)
\(\Theta(n^{\log_ba})=\Theta(n^{\log_28})=\Theta(n^3)>f(n)\)
故 \(T(n)=\Theta(n^3)\)
注意到瓶颈在于 \(a\),即矩阵乘法的运算次数。
而 Strassen 算法则通过构造 \(7\) 个矩阵,通过它们的不同组合进行加减运算,从而减少递归次数进而降低时间复杂度。上面推过,其时间复杂度为 \(T(n)=\Theta(n^{\log_27})\)
-
例题 \(6\)
「摘自《算法导论》」
\(T(n)=3T(\frac n4)+\Theta(n\log n)\)
分析:
\(a=3,b=4,f(n)=\Theta(n\log n)\)
\(\Theta(n^{\log_ba})=\Theta(n^{\log_43})<\Theta(n^{\log_44})=\Theta(n)<\Theta(n\log n)=f(n)\)
大者为王,故 \(T(n)=\Theta(n\log n)\)
实际上,这样结果虽然正确,但过程并不严谨,参见下面严谨版的定理内容。
定理内容——严谨版「《算法导论》」
定理 \(4.1\)(主定理)令 \(a\ge1\) 和 \(b>1\) 是常数,\(f(n)\) 是一个函数,\(T(n)\) 是定义在非负整数上的递归式:
\[T(n)=aT(\frac nb)+f(n) \]其中我们将 \(\frac nb\) 解释为 \(\lfloor\frac nb\rfloor\) 或 \(\lceil\frac nb\rceil\)。那么 \(T(n)\) 有如下渐进界:
- 若对某个常数 \(\epsilon>0\) 有 \(f(n)=O(n^{\log_ba-\epsilon})\),则 \(T(n)=\Theta(n^{\log_ba})\)
- 若 \(f(n)=\Theta(n^{\log_ba})\),则 \(T(n)=\Theta(n^{\log_ba}\lg n)\)
- 若对某个常数 \(\epsilon>0\) 有 \(f(n)=\Omega(n^{\log_ba+\epsilon})\),且对某个常数 \(c<1\) 和所有足够大的 \(n\) 有 \(af(\frac nb)\le cf(n)\),则 \(T(n)=\Theta(f(n))\)
在第一种情况中,不是 \(f(n)<n^{\log_ba}\) 就够了,而是要多项式意义上的小于。也就是说,\(f(n)\) 必须渐进小于 \(n^{\log_ba}\),要相差一个因子 \(n^\epsilon\),其中 \(\epsilon\) 是大于 \(0\) 的常数。
在第三种情况中,不是 \(f(n)>n^{\log_ba}\) 就够了,而是要多项式意义上的大于,而且还要满足“正则”条件 \(af(\frac nb)\le cf(n)\)。我们将会遇到的多项式界的函数中,多数都满足此条件。
注意,这三种情况并未覆盖 \(f(n)\) 的所有可能性。情况 \(1\) 和情况 \(2\) 之间有一定间隙,\(f(n)\) 可能小于 \(n^{\log_ba}\) 但不是多项式意义上的小于。类似地,情况 \(2\) 和情况 \(3\) 之间也有一定间隙,\(f(n)\) 可能大于 \(n^{\log_ba}\) 但不是多项式意义上的大于。如果函数 \(f(n)\) 落在这两个间隙中,或者情况 \(3\) 中要求的正则条件不成立,就不能用主方法来求解递归式。