关于递归式复杂度求解的一些想法。
虽然说具体数学有一整章讲渐进式,但鉴于学这个性价比太低了,基本也用不到,所以很多有关渐进式的实用知识会在本文总结。
接下来进入正题。
渐进式
很多时候,我们并不能很好的得知一个式子的封闭形式,但是对于这个式子知道他的增长速度也是极好的。这里用来表示这种渐进的符号最常用的符号有两种: \(O\) 和 \(\Theta\) 。在算法竞赛,一般直接使用 \(O\) 即可,但是在实际数学使用中两种符号其实是有区别的: \(O\) 表示的是 渐进上界(asymptotically upper bound) ,表示式子的量级不会超过这个数;而 \(\Theta\) 表示的是 渐近紧确界(asymptotically tight bound) ,也就是说记号 \(O\) 并不含式子渐进下界的信息,不过在关心时间复杂度上界的算法竞赛中下界并不重要,因此直接用 \(O\) 是没有问题的。当然还有记号 \(\Omega\) 表示 渐进下界(asymptotically lower bound) ,只不过它不太常用,我们就不对其多加讨论。
计算渐进式的方法有很多,下面会一一介绍。
主定理及其局限性
这是本文十分重要的一个点。这里先介绍一下主定理,这是一个非常好的求解递归式渐进的方法(证明略):
Theorem:
\[\begin{align*}
& 设\ a\geq1,b\geq1\ ,\ f(n)\ 为一定义在非负整数上的函数,\\
& T(n)=aT(\frac nb)+f(n)\ (当\ \frac nb\ 不为整数时代表\ \lfloor\frac nb\rfloor\ 或\ \lceil\frac nb\rceil\ ),则有:\\
\\
& 1.若存在\ \varepsilon>0\ ,\ 使得\ f(n)=O\left(n^{\log_ba-\varepsilon}\right)\ ,则\ T(n)=\Theta\left(n^{\log_ba}\right) \\
& 2.若存在\ k\geq0\ ,\ 使得\ f(n)=O\left(n^{\log_ba}\lg^kn\right)\ ,则\ T(n)=\Theta\left(n^{\log_ba}\lg^{k+1}n\right) \\
& 3.若存在\ \varepsilon>0\ ,\ 使得\ f(n)=O\left(n^{\log_ba+\varepsilon}\right)\ ,且存在\ 0<c<1\ 及正整数\ N\ ,\\&使得当\ n>N\ 时,有\ af\left(\frac nb\right)\leq cf(n)\ ,则\ T(n)=\Theta(f(n))
\end{align*}
\]
主定理的适用范围是非常广的,但是它对下面这个式子束手无策:
\[\begin{flalign}
\label{1}
T(n) = 4\sqrt nT(\sqrt n)+n
\end{flalign}
\]
发现主定理用不了,因为子问题里的 \(\sqrt n = \frac{n}{\sqrt n}\) 得到 \(b=\sqrt n\) ,这并不是一个常数,此时主定理就用不了了。
除了上式外,看到下面这个递归式:
\[\begin{flalign}
\label{2}
T(n) = T(\frac n2)+T(\frac n4)+T(\frac n8)+n
\end{flalign}
\]
这个式子的子问题并不是平均划分的,因此也就不能用主定理。
我们再看一个式子:
\[\begin{flalign}
\label{3}
T(n)=4T(\frac n4)+\frac n{\lg n}
\end{flalign}
\]
此时该式子不符合主定理的三种情况,也是不能用主定理的。
不那么暴力的暴力
对于不能用主定理解决的递归式,《算法导论》中推荐使用递归树+数学归纳法解决,但是这样工作量过于巨大了,会显得很蠢,还不如乱猜再证明来的快,此时就十分的难受,不知道怎么整。
为了让我们能更简洁与优美的暴力,采用更好的方法是必要的。我们尝试在递归树的基础上进行优化。就像记忆化搜索可以优化深度优先搜索一样,我们可以对每一个可能出现的子问题考虑它对答案的贡献。
拿 \((1)\) 式举例,我们无需列出一整棵递归树,只需对每个子问题统计贡献即可。用 \(w(x)\) 表示子问题 \(T(x)\) 的贡献,就可以有下面的表:
\(x\) |
\(w(x)\) |
\(n\) |
\(n\) |
\(\sqrt n\) |
\(4\sqrt n \times \sqrt n = 4n\) |
\(\sqrt {\sqrt n}\) |
\((4\sqrt {\sqrt n})(4\sqrt n )(\sqrt {\sqrt n}) = 16n\) |
相信对数字敏感的人无需列表即可感性地证明规律,即使不太敏感,表的前三项也足够找到规律:
\[w(n^{0.5^x}) = 4^xn
\]
那么设 \(n=2^k\) 那么层数就应该是:
\[\log_2 k = \log_2 \log_2n
\]
总的复杂度就会是:
\[\sum\limits_{i=0}^{\log_2 \log_2n}4^in
\]
而
\[\sum\limits_{i=0}^{\log_2 \log_2n}4^i \sim 4^{\log_2 \log_2n}
\]
所以答案为:
\[\begin{align*}
4^{\log_2 \log_2n}n &= 2^{2\log_2 \log_2n}n\\
&= (2^{\log_2 \log_2n})^2n\\
&= \mathcal O(n(\log^2n))
\end{align*}
\]
如果具有较好的和式计算能力的话,这种做法也是可以得出和主定理一样的结果的。下面是 Codeforces Round 960 F题 的一种错误做法最劣时间复杂度分析,它是一个基于递归的分治算法。递归树这一步就省略了,得出的式子是这样的:
\[\begin{align*}
T(n) &= \underbrace{\log_2n+2\log_2\left(\frac n2\right)+4\log_2\left(\frac n4\right)+\cdots}_{\log_2n项} \\
&= \log_2n+\sum_{k=1}^{\log_2n}2^k\log_2\left(\frac n{2^k}\right)
\end{align*}
\]
我们把后面的和式拎出来处理:
\[\begin{align*}
\sum_{k=1}^{\log_2n}2^k\log_2\left(\frac n{2^k}\right) &= \sum_{k=1}^{\log_2n}2^k(\log_2n-k)\\
&= \log_2n\sum_{k=1}^{\log_2n}2^k-\sum_{k=1}^{\log_2n}k2^k
\end{align*}
\]
具体数学一书中有对和式后面一个和式的化简,这里不赘述化简过程: \(\sum_{k=1}^{m}k2^k = (m-1)2^{m+1}+2\) ,所以:
\[\begin{align*}
\log_2n\sum_{k=1}^{\log_2n}2^k-\sum_{k=1}^{\log_2n}k2^k &= \log_2n(2^{\log_2n+1}-2)-(\log_2n-1)2^{\log_2n+1}+2 \\
&= \log_2n(2n-2)-(\log_2n-1)2n+2 \\
&= 2n\log_2n-2\log_2n-2n\log_2n+2n+2 \\
&= 2n-2\log_2n+2
\end{align*}
\]
带回到原式就有 \(2n-\log_2n+2\) ,即单次询问时间复杂度是 \(\mathcal O(n)\) 的,并不能通过。
不妨再来一组。我们来解决式 \((2)\) ,而这甚至表都可以不列出来。不难发现层数最为 \(\log_2n\) ,而层贡献为 \(层值 \times 层出现次数\) ,假设当前层为 \(n\frac 1{2^k}\) ,相当于一次能走一步、两步或三步,走 \(k\) 步的方案数, \(OGF\) 搞起,但是没搞出来(?),但是可以从组合的角度近似地看作把 \(k\) 个数分成三段,那么这个系数就应该是 \(\mathcal O(k^2)\) 的,那么答案就是:
\[\sum\limits_{i=0}^{\log_2n}i^2\frac 1{2^i}n = \mathcal O(n)
\]
最后我们来搞定式 \((3)\) ,依然是把层数算出来,是 \(\log_4n\) ,那么答案就是:
\[\sum\limits_{i=0}^{log_4n}4^i\frac {\frac n{4^i}}{\lg \frac n{4^i}} = \sum\limits_{i=0}^{log_4n} \frac {n}{\lg \frac n{4^i}}
\]
然后发现式子并不能化简到封闭形式,此时我们就需要下面的定理。
Akra-Bazzi定理
Theorem:
\[\begin{array}{l}
设\ g(x)\ 为一非负函数,\ T(x)=
\left\{\begin{array}{ll}
\Theta(1) &,1\leq x\leq X_0 \\
\sum\limits_{i=1}^ka_iT(b_ix)+g(x) &,n>X_0
\end{array}\right.\\
(其中\ k\geq 1,a_i>0,0<b_i<1),\ X_0\ 满足对任意\ 1\leq i\leq k\ 有\ X_0>\frac 1{b_i}\ 且\ X_0>\frac 1{1-b_i})\\
若\ g(x)\ 满足多项式增长条件,\ p\ 为方程\ \sum_{i=1}^ka_ib_i^p=1\ 的实数解,则\ T(x)=\Theta\left(x^p\left(1+\int_1^x\frac{g(u)}{u^{p+1}}du\right)\right)
\end{array}
\]
其中的多项式增长条件可以简易地理解为 \(\exists c>0,|g'(x)|\in \mathcal O(x^c)\) 。
开了之后我们再回到式 \((3)\) , \(p\) 显然为 \(1\) ,那么代入定理得到:
\[\begin{array}{ll}
T(n) &= \Theta\left(n\left(1+\int^n_1\frac {\frac x{\lg x}}{x^2}dx\right)\right)\\
&= \Theta\left(n\left(1+\int^n_1\frac {1}{x\lg x}dx\right)\right)\\
&= \Theta(n+(1+\lg\lg n))\\
&= \Theta(n\lg\lg n)
\end{array}
\]
Akra-Bazzi 定理原论文里的证明用了一种称为 阶变换(Order Transform) 的技巧,还涉及泛函分析,这些笔者还在学习中。当然粗暴一点的证明有数学归纳法,这里受限于本人能力不足,不做证明。
总结
总结一下,对于一般的递归式,可通过主定理求渐进解。而对于 \(b\) 不是常数的情形,可以分层分析贡献。最后对于 \(g(n)\) 满足多项式增长的式子,可以尝试使用 Akra-Bazzi 定理。