导论的第一章和第二章为基础内容,我的《算法导论》的学习将从算法的复杂性理论分析开始。

渐进记号

本节介绍了一些常见的渐进记号,并介绍了它们的使用方法。

Θ\Theta 记号

对于一个给定的函数 g(n)g(n),用 Θ(g(n))\Theta(g(n)) 来表示以下函数的集合:

Θ(g(n))={f(n)c1,c2>0,n0,s.t.nn0,0c1g(n)f(n)c2g(n)}\Theta(g(n)) = \left \{f(n)\mid \exist c_1,c_2>0,n_0,s.t.\forall n\ge n_0,0\le c_1g(n)\le f(n)\le c_2g(n)\right \}

尽管 Θ(g(n))\Theta(g(n)) 是一个集合,我们也可以使用 f(n)=Θ(g(n))f(n) = \Theta(g(n)) 来表示一个满足条件的 f(n)f(n)f(n)f(n)g(n)g(n) 的关系如下图所示:

换句话说,对所有的 nn0n\ge n_0f(n)f(n) 在一个常量因子等于 g(n)g(n),我们称 g(n)g(n)f(n)f(n) 的一个渐进紧确界。既然如此,我们完全可以抛弃这个常量因子,只从高阶项来考虑渐进关系,也就是说,Θ\Theta 记号相当于扔掉低阶项并忽略最高项前的系数。例如:

12n23n=Θ(n2)\frac{1}{2}n^2-3n=\Theta(n^2)

接下来我们形式化地用定义证明这个式子,首先先确定 c1,c2,n0c_1,c_2,n_0 这三个常量,对 nn0\forall n\ge n_0,有:

c1n212n23nc2n2c_1n^2\le \frac{1}{2}n^2-3n\le c_2n^2

也就是:

c1123nc2c_1\le \frac{1}{2}-\frac{3}{n}\le c_2

只需要选择 c1=114,c2=12,n0=7c_1=\frac{1}{14},c_2=\frac{1}{2},n_0=7 即可证明。对于其它的例子,有点类似于数学分析中极限的 ϵN\epsilon-N 语言,这里不再赘述。

OO 记号

前文的 Θ\Theta 记号相当于给出了函数在一个常量因子内的上界和下界,当只有一个渐进上界时,使用 OO 记号,也就是说,O(g(n))O(g(n)) 是如下的集合:

O(g(n))={f(n)c>0,n0,s.t.nn0,0f(n)cg(n)}O(g(n)) = \left \{f(n)\mid \exist c>0,n_0,s.t.\forall n\ge n_0,0\le f(n)\le cg(n)\right \}

我们称 g(n)g(n)f(n)f(n)渐进上界

由定义可知,Θ\Theta 记号是一个比 OO 记号更强的概念,用集合论的写法,就是 Θ(g(n))O(g(n))\Theta(g(n))\subseteq O(g(n))。同时,一个 f(n)f(n) 可以有很多个渐进上界,例如,我们很容易证明 f(n)=an+b=O(n)f(n)=an+b=O(n),也很容易证明 f(n)=an+b=O(n2)f(n)=an+b=O(n^2),我们在算法复杂度分析中更关注渐进上确界,也就是 O(n)O(n)

OO 记号太常用了,以至于我们在分析算法的时间复杂度和空间复杂度时,基本都使用 OO 记号。由于其代表的时函数的渐进上确界,在多数情况下反映的是算法的最坏情况运行时间和运行空间。例如,插入排序算法的实现中有一个双重嵌套循环,对于特定规模 nn 的输入,它的运行时间可能并不能到达 O(n2)O(n^2) 的量级,但我们说其时间复杂度为 O(n2)O(n^2),意指对于任意规模 nn 的输入,其运行时间的上确界都是 O(n2)O(n^2)

Ω\Omega 记号

类似于 OO 记号,Ω\Omega 记号表示的是算法的渐进下界,也就是如下的集合:

Ω(g(n))={f(n)c>0,n0,s.t.nn0,0cg(n)f(n)}\Omega(g(n)) = \left \{f(n)\mid \exist c>0,n_0,s.t.\forall n\ge n_0,0\le cg(n)\le f(n)\right \}

对比着看,Ω\Omega 记号可以用来表达算法最好情况下的运行时间和运行空间度,例如,插入排序的运行时间为 Ω(n)\Omega(n)

关于 Θ\Theta 记号、OO 记号和 Ω\Omega 记号,显然有如下定理:

f(n)=Θ(g(n))f(n)=O(g(n))f(n)=Ω(g(n))f(n) = \Theta(g(n)) \Leftrightarrow f(n) = O(g(n)) \text{且} f(n) = \Omega(g(n))

oo 记号

OO 记号对应,表示 O(g(n))O(g(n)) 的集合中不是渐进上确界的那一部分函数。用数学语言说,若 f(n)=o(g(n))f(n) = o(g(n)),则有:

limnf(n)g(n)=0\lim_{n\to \infty} \frac{f(n)}{g(n)}=0

ω\omega 记号

Ω\Omega 记号对应,表示 Ω(g(n))\Omega(g(n)) 的集合中不是渐进下确界的那一部分函数。用数学语言说,若 f(n)=ω(g(n))f(n) = \omega(g(n)),则有:

limnf(n)g(n)=\lim_{n\to \infty} \frac{f(n)}{g(n)}=\infty

等式和不等式中的渐进记号

渐进符号可以用于数学公式中,例如 2n2+3n+1=2n2+Θ(n)2n^2+3n+1=2n^2+\Theta(n),这是因为 3n+1=Θ(n)3n+1=\Theta(n),这就是虽然 Θ(g(n))\Theta(g(n)) 是一个集合,但是我们仍然使用 f(n)=Θ(g(n))f(n)=\Theta(g(n)) 而很少使用 f(n)Θ(g(n))f(n)\in \Theta(g(n))

这种表示是有好处的,例如在归并排序中,每一次分治地将一个大的排序问题划分成两个小的排序问题,且两个子问题和原问题的算法是一模一样的。我们用 T(n)T(n) 来表示输入规模为 nn 的算法的最坏情况运行时间,则根据归并排序的分治过程,我们有如下的递归式:

T(n)=2T(n/2)+Θ(n)T(n) = 2T(n/2)+\Theta(n)

其中,2T(n/2)2T(n/2) 表示两个子问题所需要的时间,Θ(n)\Theta(n) 表示最后的Merge操作,即将两个升序数组合并成一个升序数组的过程,这一部分的时间和问题规模成正比。

渐进记号的性质

渐进记号可以看作两个函数之间的二元关系,它具有自反性、对称性以及传递性,以 Θ\Theta 记号为例:

f(n)=Θ(g(n)),g(n)=Θ(h(n))f(n)=Θ(h(n))f(n)=Θ(f(n))f(n)=Θ(g(n))g(n)=Θ(f(n))\begin{aligned} f(n)=\Theta(g(n)),g(n) &= \Theta(h(n))\Rightarrow f(n) = \Theta(h(n)) \\ f(n) &= \Theta(f(n)) \\ f(n) = \Theta(g(n)) &\Leftrightarrow g(n) = \Theta(f(n)) \end{aligned}

但是对于 OO 记号和 Ω\Omega 记号,对称性要体现在转置上:

f(n)=O(g(n))g(n)=Ω(f(n))\begin{aligned} f(n) = O(g(n)) \Leftrightarrow g(n) = \Omega(f(n)) \end{aligned}

练习

3.1-1

不妨设 h(n)=max(f(n),g(n))h(n) = \max(f(n),g(n)),这说明 h(n)f(n)h(n)\ge f(n)h(n)g(n)h(n)\ge g(n),要证明 max(f(n),g(n))=Θ(f(n)+g(n))\max(f(n),g(n))=\Theta(f(n)+g(n)),根据定义,需要选取 c1,c2,n0c_1,c_2,n_0,使得 n>n0\forall n>n_0,有 c1(f(n)+g(n))h(n)c2(f(n)+g(n))c_1(f(n)+g(n))\le h(n)\le c_2(f(n)+g(n)),对于右边,由于 f(n),g(n)f(n),g(n) 均非负,故有 h(n)f(n)+g(n)h(n)\le f(n)+g(n),直接取 c2=1c_2=1 即可;对于右边,有 f(n)+g(n)h(n)+h(n)=2h(n)f(n)+g(n)\le h(n)+h(n)=2h(n),故直接取 c1=12c_1=\frac{1}{2} 即可。

3.1-2

需要选取 c1,c2,n0c_1,c_2,n_0,使得 c1nb(n+a)bc2nbc_1n^b\le (n+a)^b\le c_2n^b,考虑如下极限:

limn(n+a)bnb=limn(1+an)b=1\lim_{n\to \infty} \frac{(n+a)^b}{n^b}=\lim_{n\to \infty}(1+\frac{a}{n})^b=1

由极限的定义,取 ϵ=12\epsilon=\frac{1}{2},则n0\exist n_0,当 n>n0n>n_0 时,有 (n+a)bnb1<12\left | \frac{(n+a)^b}{n^b} -1 \right | <\frac{1}{2},即 12<(n+a)bnb<32\frac{1}{2}< \frac{(n+a)^b}{n^b}<\frac{3}{2},即 12nb(n+a)b32nb\frac{1}{2}n^b\le (n+a)^b\le \frac{3}{2}n^b,直接取 c1=12,c2=32c_1=\frac{1}{2},c_2=\frac{3}{2} 即可。

3.1-3

显然,因为 O(n2)O(n^2) 表示的是算法运行时间的上界,表示“最多”是这么多,而不是“至少”是这么多。

3.1-4

2n+1=22n=2O(2n)=O(2n)2^{n+1}=2\cdot 2^n=2\cdot O(2^n)=O(2^n),但是 222=16>4=222^{2\cdot 2}=16>4=2^2,故 22nO(2n)2^{2n}\ne O(2^n),事实上,22n=Ω(2n)2^{2n}=\Omega(2^n)

3.1-5

这个证明是初等的。必要性显然,充分性只需对 Ω\Omega 记号取 c1,n1c_1,n_1OO 记号取 c2,n2c_2,n_2,最后的 Θ\Theta 记号的三个常数为 c1,c2,max(n1,n2)c_1,c_2,\max(n_1,n_2) 即可。

3.1-6

这个命题也没什么好讲的,前文已经说明 O(g(n))O(g(n))Ω(g(n))\Omega(g(n)) 在算法分析中的意义。

3.1-7

从两种角度来理解,一个是直观理解:o(g(n))o(g(n)) 为除了渐进上确界以外的渐进上界,ω(g(n))\omega(g(n)) 为除了渐进下确界以外的渐进下界,它们显然没有交集。另一个是从极限角度证明:f(n)=o(g(n))f(n)=o(g(n))f(n)=ω(g(n))f(n)=\omega(g(n)) 的极限定义如下:

limnf(n)g(n)=0limnf(n)g(n)=\begin{aligned} \lim_{n\to \infty} \frac{f(n)}{g(n)}&=0 \\ \lim_{n\to \infty} \frac{f(n)}{g(n)}&=\infty \\ \end{aligned}

这两个式子显然不能同时满足。

3.1-8

类似地定义二元函数,此处略。

3.2-1

这个是高中题了,做差即可。

3.2-2

左边取以 bb 为底的对数,得到:

logb(alogbc)=logbclogba\log_b(a^{\log_bc})=\log_bc\cdot \log_ba

右边取以 bb 为底的对数,得到:

logb(clogba)=logbalogbc\log_b(c^{\log_ba})=\log_ba\cdot \log_bc

两边是完全一样的。

3.2-3

这里给出两种证明,一种是直接用放缩得到上下界:

log(n!)=i=1nlogii=1nlogn=nlogn=O(nlogn)log(n!)=log1+log2++logn2+log(n2+1)++lognlog(n2)n2=n2logn2=12nlognlog22n=Ω(nlogn)\begin{aligned} \log(n!)&=\sum_{i=1}^n\log i\le\sum_{i=1}^n\log n=n\log n=O(n\log n) \\ \log(n!)&=\log 1+\log 2+\cdots+\log \left \lfloor\frac{n}{2} \right \rfloor+\log (\left \lfloor\frac{n}{2} \right \rfloor+1)+\cdots+\log n \\ &\ge \log(\frac{n}{2})^\frac{n}{2}=\frac{n}{2}\log \frac{n}{2}=\frac{1}{2}n\log n -\frac{\log 2}{2}n=\Omega(n\log n) \end{aligned}

另一种是直接用斯特林公式:

log(n!)=12log(2πn)+nlogne+Θ(1)=12logn+nlogn+Θ(1)=Θ(nlogn)\begin{aligned} \log(n!)&=\frac{1}{2}\log(2\pi n)+n\log \frac{n}{e}+\Theta(1) \\ &=\frac{1}{2}\log n+n\log n+\Theta(1) \\ &=\Theta(n\log n) \end{aligned}

证明第二个命题,直接用放缩配合极限:

n!2nn2n22n=nn223n2=(n8)n2limn(n8)n2=\begin{aligned} \frac{n!}{2^n} \ge \frac{\frac{n}{2}^\frac{n}{2}}{2^n}&=\frac{n^\frac{n}{2}}{2^{\frac{3n}{2}}}=(\frac{n}{8})^\frac{n}{2}\\ \lim_{n\to \infty}(\frac{n}{8})^\frac{n}{2}&=\infty \\ \end{aligned}

limnn!2n=\lim_{n\to \infty}\frac{n!}{2^n}=\infty

n!=ω(2n)n!=\omega(2^n)。后半部分仍然考虑极限:

limnn!nn=limnk=1nkn\lim_{n\to \infty}\frac{n!}{n^n}=\lim_{n\to \infty}\prod_{k=1}^n\frac{k}{n}

分半考虑,对于前半段,即 k=1,2,,n2k=1,2,\cdots,\left \lfloor\frac{n}{2} \right \rfloor 有:

kn12\frac{k}{n}\le \frac{1}{2}

对于后半段,即 k=n2+1,,nk=\left \lfloor\frac{n}{2} \right \rfloor+1,\cdots,n 有:

0<kn10<\frac{k}{n}\le 1

k=1nkn12n20\prod_{k=1}^n\frac{k}{n} \le \frac{1}{2}^{\left \lfloor\frac{n}{2} \right \rfloor}\to 0

于是极限

limnn!nn=limnk=1nkn=0\lim_{n\to \infty}\frac{n!}{n^n}=\lim_{n\to \infty}\prod_{k=1}^n\frac{k}{n}=0

n!=o(nn)n!=o(n^n)