算法导论——函数的增长
上次提到的增长量级简单的说明了算法的效率,而且还使用了一个符号。这次给出这个符号更精确的定义。在数学上,运行时间随着数据量增长的规律可以看为一个函数,因此这篇文章主要从更全局泛化的谈谈函数的增长。
当输入规模足够大,使得只有运行时间的增长量级有关时,我们需要研究算法的渐近效率。这里的渐近效率指的是当输入规模无限增加时,在极限中,算法的运行时间如何随着输入规模的变大而增加。下面首先定义几种监禁记号。
渐近记号
一般来说,对于运行时间函数的定义域是,也就是自然数。但是渐近记号也可以活用到实数域,或者限制到自然数的一个子集。对于上次插入排序的分析,我们知道它的运行时间可以被总结为,我们将它记为。渐近记号除了用于表示运行时间,也可以用来分析运行空间,甚至是与算法无关的函数的分析。
虽然之前关注的情况是最坏情况的输入,但是分析时我们希望能用渐近记号来描述任何输入,下面介绍几种不同渐近记号的定义。
记号
对于一个给定函数,那么表示的是以下函数的集合: 这里可以看到,我们通过函数和两个常数,给了函数一个上界和一个下界。由于是一个集合,因此更严格的写法是:,但是我们简单记成,这样更简便。对于所有,函数在一个常量因子内等于。我们称是的一个渐近紧确界。
的定义要求每个成员都渐近非负,这意味着也需渐近非负。这个假设对其他的渐近记号也是一样成立。 ### 记号 ### 记号渐近地给出一个函数的上界和下界,当只有一个渐近上界时,我们使用记号。表示的是以下函数的集合:
由于记号的定义,下面这个式子也是正确的:,因为给出的是上界,但是并不要求是多么确切的上界。
使用记号的好处是,它用来表示最坏情况下的运行时间时,对任何输入都是正确的。比如之前的插入排序,我们说了,最坏情况下运行时间为,但这并不意味着对于所有输入都满足,因为在最好的情况下运行时间为,如果使用来描述则可以拓展到任意输入,因为不管最坏的情况就是给了一个上界。 ### 记号 ###
不用说,记号给出的就是渐近下届:
记号就适用于最优情况,例如插入排序,我们可以指出它的运行时间为,但是说它最坏情况下运行时间为也是正确的。显然记号的实际应用是最少的。
等式与不等式中的渐近记号
使用渐近记号是非常有用的。我们以后可能会使用类似下式的“奇怪”写法: 这样的写法是没有问题。我们可以使用渐近记号消除一个等式中无关紧要的细节。例如之前的归并排序,我们可以将最坏情况的运行时间写成递归式: (一个归并排序被分解成两个序列的归并排序,在加上一个的合并消耗)
后者用渐近记号表示的函数成为匿名函数。一个表达式中匿名函数的数目可以理解为等于渐近记号出现的次数。比如在表达式中,只有一个匿名函数,而不同于下式: 实际上后者并没有什么清晰的解释(对于前者我的理解是就像插入排序最坏的情况一样,只有一个匿名函数,最后运行时间是)。
有时候渐近记号也出现在等式的左边: 总之在等式链中渐近记号的使用是非常灵活的,而这也让我们能更关注于最需要关心的部分。 ### 记号 ### 之前说的记号,表示的渐近上界,它有可能是一个紧确的,也可能不是,例如就不是紧确的,而则是紧确的。因此这里再引入一个新的渐近记号,表示的是非渐近紧确上界。我们定义如下: 举个例子:是正确的,但是就是错误的了。 直观上来说,对于渐近记号,当趋于无穷时,函数相对于就微不足道了,即: 实际上我们在数学中经常看到用来表示高阶无穷小,也符合上述的等式,但是在这里,我们依然限定函数是渐近非负的。
###记号 ### 记号与记号的关系类似于记号与记号的关系。表示一个非渐近紧确的下界:
举个例子:是正确的,但是是错误的。同样,直观上讲,对于渐近记号,当趋于无穷时,函数相对于函数就微不足道了:
对于两个函数的渐近比较和两个实数的比较之间可以做一个类比: $$ 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 $$
但是需要注意的是,任意两个实数一定满足于三种情况的一种,但是不是任意的函数之间都可以做渐近比较,例如我们不能渐近比较与。
其他
除了渐近记号,这一章还介绍了一些其他的概念,单调性,向上向下取整,模运算等等。对于这些基本的知识这篇文章就不多提了,仅写一些平时不容易被注意到知识点。
阶乘
实际上阶乘是一个很简单的概念,定义如下:
我在这里要介绍的是阶乘有一个斯特林近似公式:
它给出了一个更紧致的上界和下届。实际上我们很容易得到: $$ n!=o(n^n)\\ n!=\omega(2^n)\\ $$ 而通过斯特林公式可以进一步证明: 对于所有,也有: 其中。
多重函数
我们使用记号来表示函数多次作用于一个初值上:对于非负数: $$ f^{(i)}(n) = \{ \begin{array} n & i=0\\ f(f^{(i-1)}(n)) & i>0 \end{array} $$ 例如,则。 ### 多重对数函数 ###
我们使用来表示多重对数。假设定义如上,那么多重对数函数定义为: 多重对数函数增长非常缓慢: $$ \text{lg}^*2 = 1\\ \text{lg}^*4 = 2\\ \text{lg}^*16 = 3\\ \text{lg}^*65536=4\\ \text{lg}^*2^{65536}=5 $$ 实际中,我们很难遇到的规模。
斐波那契数
我们一直知道斐波那契数列是以指数形式增长的,这里从另一个更新奇的角度说明这个问题。 $$ F_0=0\\ F_1 = 1\\ F_i = F_ {i-1} + F_ {i-2},i\ge 2 $$
实际上斐波那契数列与黄金分割率以及其共轭数有关,他们是下面方程的两个根: 并且: 我们有: 由于后一项随着增大可忽略不计,因此从上式可以看出,斐波那契数是指数形式增长的。有趣的是当增大时,前一项与后一项之比越来越趋于黄金分割。