算法导论——函数的增长

上次提到的增长量级简单的说明了算法的效率,而且还使用了一个符号Θ\Theta。这次给出这个符号更精确的定义。在数学上,运行时间随着数据量增长的规律可以看为一个函数,因此这篇文章主要从更全局泛化的谈谈函数的增长。

当输入规模足够大,使得只有运行时间的增长量级有关时,我们需要研究算法的渐近效率。这里的渐近效率指的是当输入规模无限增加时,在极限中,算法的运行时间如何随着输入规模的变大而增加。下面首先定义几种监禁记号。

渐近记号

一般来说,对于运行时间函数的定义域是={0,1,2,....}\mathbb N = \{0,1,2,....\},也就是自然数。但是渐近记号也可以活用到实数域,或者限制到自然数的一个子集。对于上次插入排序的分析,我们知道它的运行时间可以被总结为an2+bn+can^2 +bn + c,我们将它记为Θ(n2)\Theta(n^2)。渐近记号除了用于表示运行时间,也可以用来分析运行空间,甚至是与算法无关的函数的分析。

虽然之前关注的情况是最坏情况的输入,但是分析时我们希望能用渐近记号来描述任何输入,下面介绍几种不同渐近记号的定义。

Θ\Theta记号

对于一个给定函数g(n)g(n),那么Θ(g(n))\Theta(g(n))表示的是以下函数的集合: Θ(g(n))={f(n):c1,c2,n0,使nn0,0c1g(n)f(n)c2g(n)} \Theta(g(n)) = \{f(n):存在正常量c_1,c_2,n_0,使得对所有n\ge n_0,有0\leq c_1g(n)\leq f(n) \leq c_2 g(n)\} 这里可以看到,我们通过函数g(n)g(n)和两个常数c1,c2c_1,c_2,给了函数f(n)f(n)一个上界和一个下界。由于Θ(n)\Theta(n)是一个集合,因此更严格的写法是:f(n)Θ(g(n))f(n) \in \Theta(g(n)),但是我们简单记成f(n)=Θ(g(n))f(n) = \Theta(g(n)),这样更简便。对于所有nn0n \ge n_0,函数f(n)f(n)在一个常量因子内等于g(n)g(n)。我们称g(n)g(n)f(n)f(n)的一个渐近紧确界

Θ(g(n))\Theta(g(n))的定义要求每个成员f(n)Θ(g(n))f(n)\in \Theta(g(n))都渐近非负,这意味着g(n)g(n)也需渐近非负。这个假设对其他的渐近记号也是一样成立。 ### OO记号 ### Θ\Theta记号渐近地给出一个函数的上界和下界,当只有一个渐近上界时,我们使用OO记号。O(g(n))O(g(n))表示的是以下函数的集合: O(g(n))={f(n):c,n0,使nn0,0f(n)cg(n)} O(g(n)) = \{f(n):存在正常量c,n_0,使得对所有n\ge n_0,有0 \leq f(n) \leq c g(n)\}

由于OO记号的定义,下面这个式子也是正确的:n=O(n2)n = O(n^2),因为O(n2)O(n^2)给出的是上界,但是并不要求是多么确切的上界。

使用OO记号的好处是,它用来表示最坏情况下的运行时间时,对任何输入都是正确的。比如之前的插入排序,我们说了,最坏情况下运行时间为Θ(n2)\Theta(n^2),但这并不意味着对于所有输入都满足,因为在最好的情况下运行时间为Θ(n)\Theta(n),如果使用OO来描述则可以拓展到任意输入,因为不管最坏的情况就是给了一个上界。 ### Ω\Omega记号 ###

不用说,Ω\Omega记号给出的就是渐近下届: Ω(g(n))={f(n):c,n0,使nn0,0cg(n)f(n)} \Omega(g(n)) = \{f(n):存在正常量c,n_0,使得对所有n\ge n_0,有0\leq cg(n)\leq f(n) \}

Ω\Omega记号就适用于最优情况,例如插入排序,我们可以指出它的运行时间为Ω(n)\Omega(n),但是说它最坏情况下运行时间为Ω(n2)\Omega(n^2)也是正确的。显然Ω\Omega记号的实际应用是最少的。

等式与不等式中的渐近记号

使用渐近记号是非常有用的。我们以后可能会使用类似下式的“奇怪”写法: 2n2+3n+1=2n2+Θ(n) 2n^2 + 3n + 1 = 2n^2 + \Theta(n) 这样的写法是没有问题。我们可以使用渐近记号消除一个等式中无关紧要的细节。例如之前的归并排序,我们可以将最坏情况的运行时间写成递归式: T(n)=2T(n2)+Θ(n) T(n) = 2T(\frac{n}{2})+ \Theta(n) (一个归并排序被分解成两个序列的归并排序,在加上一个Θ(n)\Theta(n)的合并消耗)

后者用渐近记号表示的函数成为匿名函数。一个表达式中匿名函数的数目可以理解为等于渐近记号出现的次数。比如在表达式i=1nO(i)\sum_ {i=1}^nO(i)中,只有一个匿名函数O(i)O(i),而不同于下式: O(1)+O(2)+...+O(n) O(1)+O(2)+...+O(n) 实际上后者并没有什么清晰的解释(对于前者我的理解是就像插入排序最坏的情况一样,只有一个匿名函数,最后运行时间是nO(n)=O(n2)nO(n) = O(n^2))。

有时候渐近记号也出现在等式的左边: 2n2+Θ(n)=Θ(n2) 2n^2 + \Theta(n) = \Theta(n^2) 总之在等式链中渐近记号的使用是非常灵活的,而这也让我们能更关注于最需要关心的部分。 ### oo记号 ### 之前说的OO记号,表示的渐近上界,它有可能是一个紧确的,也可能不是,例如2n=O(n2)2n = O(n^2)就不是紧确的,而2n2=O(n2)2n^2 = O(n^2)则是紧确的。因此这里再引入一个新的渐近记号oo,表示的是非渐近紧确上界。我们定义o(g(n))o(g(n))如下: o(g(n))={f(n):c>0,n0>0,使nn0,0f(n)<cg(n)} o(g(n)) = \{f(n): 对任意常量c>0,存在常量n_0>0,使得对于所有n\ge n_0,有0\leq f(n) < cg(n) \} 举个例子:2n=o(n2)2n = o(n^2)是正确的,但是2n2=o(n2)2n^2 = o(n^2)就是错误的了。 直观上来说,对于渐近记号oo,当nn趋于无穷时,函数f(n)f(n)相对于g(n)g(n)就微不足道了,即: limnf(n)g(n)=0 \lim_ {n\rightarrow \infty} \frac{f(n)}{g(n)} = 0 实际上我们在数学中经常看到用oo来表示高阶无穷小,也符合上述的等式,但是在这里,我们依然限定函数是渐近非负的。

###ω\omega记号 ### ω\omega记号与Ω\Omega记号的关系类似于oo记号与OO记号的关系。ω\omega表示一个非渐近紧确的下界: ω(g(n))={f(n):c>0,n0>0,使nn0,0cg(n)<f(n)} \omega(g(n)) = \{f(n): 对任意常量c>0,存在常量n_0>0,使得对于所有n\ge n_0,有0\leq cg(n) < f(n) \}

举个例子:n2=ω(n)n^2 = \omega(n)是正确的,但是2n=ω(n)2n = \omega(n)是错误的。同样,直观上讲,对于渐近记号ω\omega,当nn趋于无穷时,函数g(n)g(n)相对于函数f(n)f(n)就微不足道了: limnf(n)g(n)= \lim_ {n\rightarrow \infty} \frac{f(n)}{g(n)} = \infty

对于两个函数f,gf,g的渐近比较和两个实数a,ba,b的比较之间可以做一个类比: $$ f(n) = O(g(n)) \rightarrow a\leq b\\ f(n) = o(g(n)) \rightarrow a < b\\ f(n) = \Omega(g(n)) \rightarrow a\ge b\\ f(n) = \omega(g(n)) \rightarrow a > b\\ f(n) = \Theta(g(n)) \rightarrow a = b $$

但是需要注意的是,任意两个实数a,ba,b一定满足于a>b,a<b,a=ba>b,a < b,a =b三种情况的一种,但是不是任意的函数之间都可以做渐近比较,例如我们不能渐近比较n2n^2n1+sinnn^{1+\sin n}

其他

除了渐近记号,这一章还介绍了一些其他的概念,单调性,向上向下取整,模运算等等。对于这些基本的知识这篇文章就不多提了,仅写一些平时不容易被注意到知识点。

阶乘

实际上阶乘是一个很简单的概念,定义如下: n!=n(n1)...1 n!=n\cdot(n-1)\cdot...\cdot 1

我在这里要介绍的是阶乘有一个斯特林近似公式: n!=2πn(ne)n(1+Θ(1n)) n!=\sqrt{2\pi n}\left(\frac{n}{e}\right)^n\left(1 + \Theta(\frac{1}{n})\right)

它给出了一个更紧致的上界和下届。实际上我们很容易得到: $$ n!=o(n^n)\\ n!=\omega(2^n)\\ $$ 而通过斯特林公式可以进一步证明: lg(n!)=Θ(nlgn) \text{lg}(n!) = \Theta(n\text{lg}n) 对于所有n1n\ge 1,也有: n!=2πn(ne)nean n!=\sqrt{2\pi n}\left(\frac{n}{e}\right)^ne^{a_n} 其中112n+1<an<112n\frac{1}{12n+1}< a_n < \frac{1}{12n}

多重函数

我们使用记号f(i)(n)f^{(i)}(n)来表示函数f(n)f(n)多次作用于一个初值nn上:对于非负数ii: $$ f^{(i)}(n) = \{ \begin{array} n & i=0\\ f(f^{(i-1)}(n)) & i>0 \end{array} $$ 例如f(n)=2nf(n) = 2n,则f(i)(n)=2inf^{(i)}(n) = 2^in。 ### 多重对数函数 ###

我们使用lg*\text{lg}^*来表示多重对数。假设lg(i)\lg^{(i)}定义如上,那么多重对数函数定义为: lg*=min{i0:lg(i)n1} \text{lg}^* = \min\{i\ge 0:\text{lg}^{(i)}n\leq 1\} 多重对数函数增长非常缓慢: $$ \text{lg}^*2 = 1\\ \text{lg}^*4 = 2\\ \text{lg}^*16 = 3\\ \text{lg}^*65536=4\\ \text{lg}^*2^{65536}=5 $$ 实际中,我们很难遇到n>5n>5的规模。

斐波那契数

我们一直知道斐波那契数列是以指数形式增长的,这里从另一个更新奇的角度说明这个问题。 $$ F_0=0\\ F_1 = 1\\ F_i = F_ {i-1} + F_ {i-2},i\ge 2 $$

实际上斐波那契数列与黄金分割率ϕ\phi以及其共轭数ϕ̂\hat\phi有关,他们是下面方程的两个根: x2=x+1 x^2 = x+1 并且: ϕ=1+52,ϕ̂=152 \phi = \frac{1+\sqrt 5}{2},\hat\phi = \frac{1-\sqrt 5}{2} 我们有: Fi=ϕiϕ̂i5 F_i = \frac{\phi^i - \hat\phi^i}{\sqrt 5} 由于后一项随着ii增大可忽略不计,因此从上式可以看出,斐波那契数是指数形式增长的。有趣的是当nn增大时,前一项与后一项之比越来越趋于黄金分割。