3.1 渐近记号
用来描述算法渐近运行时间的记号根据定义域为自然数集N={0,1,2,…}的函数来定义。这样的记号对描述最坏情况运行时间函数T(n)是方便的,因为该函数通常只定义在整数输入规模上。然而,我们发现有时按各种方式活用渐近记号是方便的。例如,我们可以扩展该记号到实数域或者选择性地限制其到自然数的一个子集。
43然而,我们应该确保能理解该记号的精确含义,以便在活用时不会误用它。本节将定义一些基本的渐近记号,并介绍一些常见的活用法。
渐近记号、函数与运行时间
正如我们写插入排序的最坏情况运行时间为Θ(n2)时那样,我们将主要使用渐近记号来描述算法的运行时间。然而,渐近记号实际上应用于函数。回顾一下,我们曾把插入排序的最坏情况运行时间刻画为an2+bn+c,其中a、b和c是常量。通过把插入排序的运行时间写成Θ(n2),我们除去了该函数的某些细节。因为渐近记号适用于函数,我们所写成的Θ(n2)就是函数an2+bn+c,所以上述情况碰巧刻画了插入排序的最坏情况运行时间。
本书中对其使用渐近记号的函数通常刻画算法的运行时间。但是渐近记号也可以适用于刻画算法的某个其他方面(例如,算法使用的空间数量)的函数,甚至可以适用于和算法没有任何关系的函数。
即使我们使用渐近记号来刻画算法的运行时间,我们也需要了解意指哪个运行时间。有时我们对最坏情况运行时间感兴趣。然而,我们常常希望刻画任何输入的运行时间。换句话说,我们常常希望做出一种综合性地覆盖所有输入而不仅仅是最坏情况的陈述。我们将看到完全适合刻画任何输入的运行时间的渐近记号。
在集合记号中,冒号意指“使得”。
Θ记号
在第2章,我们发现插入排序的最坏情况运行时间为T(n)=Θ(n2)。让我们来定义这个记号意指什么。对一个给定的函数g(n),用Θ(g(n))来表示以下函数的集合:
44Θ(g(n))={f(n):存在正常量c1、c2和n0,使得对所有n≥n0,有0≤c1g(n)≤f(n)≤c2g(n)}
若存在正常量c1和c2,使得对于足够大的n,函数f(n)能“夹入”c1g(n)与c2g(n)之间,则f(n)属于集合Θ(g(n))。因为Θ(g(n))是一个集合,所以可以记“f(n)∈Θ(g(n))”,以指出f(n)是Θ(g(n))的成员。作为替代,我们通常记“f(n)=Θ(g(n))”以表达相同的概念。因为我们按这种方式活用了等式,所以你可能感到困惑,但是在本节的后面我们将看到这样做有其好处。
图3-1(a)给出了函数f(n)与g(n)的一幅直观画面,其中f(n)=Θ(g(n))。对在n0及其右边n的所有值,f(n)的值位于或高于c1g(n)且位于或低于c2g(n)。换句话说,对所有n≥n0,函数f(n)在一个常量因子内等于g(n)。我们称g(n)是f(n)的一个渐近紧确界(asymptotically tight bound)。
图3-1 Θ、O和Ω记号的图例。在每个部分,标出的n0的值是最小的可能值,任何更大的值也将有效。(a)Θ记号限制一个函数在常量因子内。如果存在正常量n0、c1和c2,使得在n0及其右边,f(n)的值总位于c1g(n)与c2g(n)之间或等于它们,那么记f(n)=Θ(g(n))。(b)O记号为函数给出一个在常量因子内的上界。如果存在正常量n0和c,使得在n0及其右边,f(n)的值总小于或等于cg(n),那么记f(n)=O(g(n))。(c)Ω记号为函数给出一个在常量因子内的下界。如果存在正常量n0和c,使得在n0及其右边,f(n)的值总大于或等于cg(n),那么记f(n)=Ω(g(n))
Θ(g(n))的定义要求每个成员f(n)∈Θ(g(n))均渐近非负,即当n足够大时,f(n)非负。(渐近正函数就是对所有足够大的n均为正的函数。)因此,函数g(n)本身必为渐近非负,否则集合Θ(g(n))为空。所以我们假设用在Θ记号中的每个函数均渐近非负。这个假设对本章定义的其他渐近记号也成立。
45
在第2章,我们介绍了Θ记号的一种非形式化的概念,相当于扔掉低阶项并忽略最高阶项前的系数。让我们通过使用形式化定义证明12n2-3n=Θ(n2)来简要地证实这种直觉。为此,我们必须确定正常量c1、c2和n0,使得对所有n≥n0,有:
c1n2≤12n2-3n≤c2n2
用n2除上式得:
c1≤12-3n≤c2
通过选择任何常量c2≥1/2,可以使右边的不等式对任何n≥1的值成立。同样,通过选择任何常量c1≤1/14,可以使左边的不等式对任何n≥7的值成立。因此,通过选择c1=1/14,c2=1/2且n0=7,可以证明12n2-3n=Θ(n2)。当然,还存在对这些常量的其他选择,但是重要的是存在某个选择。要注意的是,这些常量依赖于函数12n2-3n;属于Θ(n2)的不同函数通常需要不同的常量。
我们还可以使用形式化定义来证明6n3≠Θ(n2)。采用反证法,假设存在c2和n0,使得对所有n≥n0,有6n3≤c2n2。然而用n2除该式,得n≤c2/6,因为c2为常量,所以对任意大的n,该不等式不可能成立。
直觉上,一个渐近正函数的低阶项在确定渐近确界时可以被忽略,因为对大的n,它们是无足轻重的。当n较大时,即使最高阶项的一个很小的部分都足以支配所有低阶项。因此,将c1置为稍小于最高阶项系数的值并将c2置为稍大于最高阶项系数的值能使Θ记号定义中的不等式得到满足。最高阶项系数同样可以被忽略,因为它仅仅根据一个等于该系数的常量因子来改变c1和c2。
作为一个例子,考虑任意二次函数f(n)=an2+bn+c,其中a、b和c均为常量且a>0。扔掉低阶项并忽略常量后得f(n)=Θ(n2)。为了形式化地证明相同的结论,我们取常量c1=a/4,c2=7a/4且n0=2·max(b/a,c/a)。可以证明对所有n≥n0,有0≤c1n2≤an2+bn+c≤c2n2。一般来说,对任意多项式p(n)=∑di=0aini,其中ai为常量且ad>0,我们有p(n)=Θ(nd)(参见思考题3-1)。
因为任意常量是一个0阶多项式,所以可以把任意常量函数表示成Θ(n0)或Θ(1)。
46然而,后一种记号是一种轻微的活用,因为该表达式并未指出什么变量趋于无穷。我们将经常使用记号Θ(1)来意指一个常量或者关于某个变量的一个常量函数。
真正的问题是通常的函数记号没有区分函数与函数值。在λ演算中,函数的参数被清楚地说明:函数n2可被写成λn.n2,或者甚至写成λr.r2。然而,采用一种更严格的记号将使代数操作复杂化,所以我们选择容忍这种活用。
O记号
Θ记号渐近地给出一个函数的上界和下界。当只有一个渐近上界时,使用O记号。对于给定的函数g(n),用O(g(n))(读作“大Og(n)”,有时仅读作“Og(n)”)来表示以下函数的集合:
O(g(n))={f(n):存在正常量c和n0,使得对所有n≥n0,有0≤f(n)≤cg(n)}
我们使用O记号来给出函数的一个在常量因子内的上界。图3-1(b)展示了O记号背后的直觉知识。对在n0及其右边的所有值n,函数f(n)的值总小于或等于cg(n)。
我们记f(n)=O(g(n))以指出函数f(n)是集合O(g(n))的成员。注意,f(n)=Θ(g(n))蕴涵着f(n)=O(g(n)),因为Θ记号是一个比O记号更强的概念。按集合论中的写法,我们有Θ(g(n))O(g(n))。因此,关于任意二次函数an2+bn+c,其中a>0,在Θ(n2)中的证明也证明了任意这样的二次函数在O(n2)中。也许更令人惊奇的是当a>0时,任意线性函数an+b也在O(n2)中,通过取c=a+b和n0=max(1,-b/a),可以很容易证明这个结论。
如果你以前见过O记号,你会发现我们这样书写(如n=O(n2))很奇怪。在文献中,有时我们发现O记号非形式化地描述渐近确界,即已经使用Θ记号定义的东西。然而,本书中当书写f(n)=O(g(n))时,我们仅仅要求g(n)的某个常量倍数是f(n)的渐近上界,而不要求它是一个多么紧确的上界。在算法文献中,标准的做法是区分渐近上界与渐近确界。
使用O记号,我们常常可以仅仅通过检查算法的总体结构来描述算法的运行时间。例如,第2章中插入排序算法的双重嵌套循环结构对最坏情况运行时间立即产生一个O(n2)的上界:内层循环每次迭代的代价以O(1)(常量)为上界,下标i和j均最多为n,
47对于n2个i和j值对的每一对,内循环最多执行一次。
既然O记号描述上界,那么当用它来限制算法的最坏情况运行时间时,关于算法在每个输入上的运行时间,我们也有一个界,这就是前面讨论的综合性陈述。因此,对插入排序的最坏情况运行时间的界O(n2)也适用于该算法对每个输入的运行时间。然而,对插入排序的最坏情况运行时间的界Θ(n2)并未暗示插入排序对每个输入的运行时间的界也是Θ(n2)。例如,我们在第2章曾看到当输入已排好序时,插入排序的运行时间为Θ(n)。
从技术上看,称插入排序的运行时间为O(n2)有点不合适,因为对给定的n,实际的运行时间是变化的,依赖于规模为n的特定输入。当我们说“运行时间为O(n2)”时,意指存在一个O(n2)的函数f(n),使得对n的任意值,不管选择什么特定的规模为n的输入,其运行时间的上界都是f(n)。这也就是说最坏情况运行时间为O(n2)。
Ω记号
正如O记号提供了一个函数的渐近上界,Ω记号提供了渐近下界。对于给定的函数g(n),用Ω(g(n))(读作“大Ωg(n)”,有时仅读作“Ωg(n)”)来表示以下函数的集合:
Ω(g(n))={f(n):存在正常量c和n0,使得对所有n≥n0,有0≤cg(n)≤f(n)}
图3-1(c)给出了Ω记号的直观解释。对在n0及其右边的所有值n,f(n)的值总大于或等于cg(n)。
根据目前所看到的这些渐近记号的定义,容易证明以下重要定理(参见练习3.1-5)。
定理3.1 对任意两个函数f(n)和g(n),我们有f(n)=Θ(g(n)),当且仅当f(n)=O(g(n))且f(n)=Ω(g(n))。
作为应用本定理的一个例子,关于对任意常量a、b和c,其中a>0,有an2+bn+c=Θ(n2)的证明直接蕴涵an2+bn+c=Ω(n2)和an2+bn+c=O(n2)。实际上不是像该例子中所做的,应用定理3.1从渐近确界获得渐近上界和下界,而是通常用它从渐近上界和下界来证明渐近确界。
48
当称一个算法的运行时间(无修饰语)为Ω(g(n))时,我们意指对每个n值,不管选择什么特定的规模为n的输入,只要n足够大,对那个输入的运行时间至少是g(n)的常量倍。等价地,我们再对一个算法的最好情况运行时间给出一个下界。例如,插入排序的最好情况运行时间为Ω(n),这蕴涵着插入排序的运行时间为Ω(n)。
所以插入排序的运行时间介于Ω(n)和O(n2),因为它落入n的线性函数与n的二次函数之间的任何地方。而且,这两个界是尽可能渐近地紧确的:例如,插入排序的运行时间不是Ω(n2),因为存在一个输入(例如,当输入已排好序时),对该输入,插入排序在Θ(n)时间内运行。然而,这与称插入排序的最坏情况运行时间为Ω(n2)并不矛盾,因为存在一个输入,使得该算法需要Ω(n2)的时间。
等式和不等式中的渐近记号
我们已经看到渐近记号可以如何用于数学公式中。例如,在介绍O记号时,记“n=O(n2)”。我们还可能写过2n2+3n+1=2n2+Θ(n)。如何解释这样的公式呢?
当渐近记号独立于等式(或不等式)的右边(即不在一个更大的公式内)时,如在n=O(n2)中,我们已经定义等号意指集合的成员关系:n∈O(n2)。然而,一般来说,当渐近记号出现在某个公式中时,我们将其解释为代表某个我们不关注名称的匿名函数。例如,公式2n2+3n+1=2n2+Θ(n)意指2n2+3n+1=2n2+f(n),其中f(n)是集合Θ(n)中的某个函数。在这个例子中,假设f(n)=3n+1,该函数确实在Θ(n)中。
按这种方式使用渐近记号可以帮助消除一个等式中无关紧要的细节与混乱。例如,在第2章中,我们把归并排序的最坏情况运行时间表示为递归式
T(n)=2T(n/2)+Θ(n)
如果只对T(n)的渐近行为感兴趣,那么没有必要准确说明所有低阶项,它们都被理解为包含在由项Θ(n)表示的匿名函数中。
一个表达式中匿名函数的数目可以理解为等于渐近记号出现的次数。例如,在表达式∑ni=1O(i)中,只有一个匿名函数(一个i的函数)。
49因此,这个表达式不同于O(1)+O(2)+…+O(n),实际上后者没有一个清晰的解释。
在某些例子中,渐近记号出现在等式的左边,例如
2n2+Θ(n)=Θ(n2)
我们使用以下规则来解释这种等式:无论怎样选择等号左边的匿名函数,总有一种办法来选择等号右边的匿名函数使等式成立。因此,我们的例子意指对任意函数f(n)∈Θ(n),存在某个函数g(n)∈Θ(n2),使得对所有的n,有2n2+f(n)=g(n)。换句话说,等式右边比左边提供的细节更粗糙。
我们可以将许多这样的关系链在一起,例如
2n2+3n+1=2n2+Θ(n)=Θ(n2)
可以用上述规则分别解释每个等式。第一个等式表明存在某个函数f(n)∈Θ(n),使得对所有的n,有2n2+3n+1=2n2+f(n)。第二个等式表明对任意函数g(n)∈Θ(n)(如刚刚提到的f(n)),存在某个函数h(n)∈Θ(n2),使得对所有的n,有2n2+g(n)=h(n)。注意,这种解释蕴涵着2n2+3n+1=Θ(n2),这就是等式链直观上提供给我们的东西。
o记号
由O记号提供的渐近上界可能是也可能不是渐近紧确的。界2n2=O(n2)是渐近紧确的,但是界2n=O(n2)却不是。我们使用o记号来表示一个非渐近紧确的上界。形式化地定义o(g(n))(读作“小og(n)”)为以下集合:
o(g(n))={f(n):对任意正常量c>0,存在常量n0>0,使得对所有n≥n0,有0≤f(n)
例如,2n=o(n2),但是2n2≠o(n2)。
O记号与o记号的定义类似。主要的区别是在f(n)=O(g(n))中,界0≤f(n)≤cg(n)对某个常量c>0成立,但在f(n)=o(g(n))中,界0≤f(n)0成立。直观上,在o记号中,当n趋于无穷时,函数f(n)相对于g(n)来说变得微不足道了,
50即
limn→∞f(n)g(n)=0(3.1)
有些学者使用这个极限作为o记号的定义;本书中的定义还限定匿名函数是渐近非负的。
ω记号
ω记号与Ω记号的关系类似于o记号与O记号的关系。我们使用ω记号来表示一个非渐近紧确的下界。定义它的一种方式是:
f(n)∈ω(g(n))当且仅当g(n)∈o(f(n))
然而,我们形式化地定义ω(g(n))(读作“小ωg(n)”)为以下集合:
ω(g(n))={f(n):对任意正常量c>0,存在常量n0>0,使得对所有n≥n0,有0≤cg(n)<f(n)}
例如,n2/2=ω(n),但是n2/2≠ω(n2)。关系f(n)=ω(g(n))蕴涵着
limn→∞f(n)g(n)=∞
也就是说,如果这个极限存在,那么当n趋于无穷时,f(n)相对于g(n)来说变得任意大了。
比较各种函数
实数的许多关系性质也适用于渐近比较。下面假定f(n)和g(n)渐近为正。
传递性:
f(n)=Θ(g(n))且g(n)=Θ(h(n)) 蕴涵f(n)=Θ(h(n))
f(n)=O(g(n))且g(n)=O(h(n)) 蕴涵f(n)=O(h(n))
f(n)=Ω(g(n))且g(n)=Ω(h(n)) 蕴涵f(n)=Ω(h(n))
f(n)=o(g(n))且g(n)=o(h(n)) 蕴涵f(n)=o(h(n))
f(n)=ω(g(n))且g(n)=ω(h(n)) 蕴涵f(n)=ω(h(n))
自反性:
f(n)=Θ(f(n))
f(n)=O(f(n))
51f(n)=Ω(f(n))
对称性:
f(n)=Θ(g(n))当且仅当g(n)=Θ(f(n))
转置对称性:
f(n)=O(g(n))当且仅当g(n)=Ω(f(n))
f(n)=o(g(n))当且仅当g(n)=ω(f(n))
因为这些性质对渐近记号成立,所以可以在两个函数f与g的渐近比较和两个实数a与b的比较之间做一种类比。
f(n)=O(g(n))类似于a≤b
f(n)=Ω(g(n))类似于a≥b
f(n)=Θ(g(n))类似于a=b
f(n)=o(g(n))类似于a
f(n)=ω(g(n))类似于a>b
若f(n)=o(g(n)),则称f(n)渐近小于g(n);若f(n)=ω(g(n)),则称f(n)渐近大于g(n)。
然而,实数的下列性质不能携带到渐近记号:
三分性 对任意两个实数a和b,下列三种情况恰有一种必须成立:ab。
虽然任意两个实数都可以进行比较,但不是所有函数都可渐近比较。也就是说,对两个函数f(n)和g(n),也许f(n)=O(g(n))和f(n)=Ω(g(n))都不成立。例如,我们不能使用渐近记号来比较函数n和n1+sin n,因为n1+sin n中的幂值在0与2之间摆动,取介于两者之间的所有值。
练习
3.1-1 假设f(n)与g(n)都是渐近非负函数。使用Θ记号的基本定义来证明max(f(n),g(n))=Θ(f(n)+g(n))。
3.1-2 证明:对任意实常量a和b,其中b>0,有
52(n+a)b=Θ(nb)(3.2)
3.1-3 解释为什么“算法A的运行时间至少是O(n2)”这一表述是无意义的。
3.1-4 2n+1=O(2n)成立吗?22n=O(2n)成立吗?
3.1-5 证明定理3.1。
3.1-6 证明:一个算法的运行时间为Θ(g(n))当且仅当其最坏情况运行时间为O(g(n)),且其最好情况运行时间为Ω(g(n))。
3.1-7 证明:o(g(n))∩ω(g(n))为空集。
3.1-8 可以扩展我们的记号到有两个参数n和m的情形,其中的n和m可以按不同速率独立地趋于无穷。对于给定的函数g(n,m),用O(g(n,m))来表示以下函数集:
O(g(n,m))={f(n,m):存在正常量c、n0和m0,使得对所有n≥n0或m≥m0,
有0≤f(n,m)≤cg(n,m)}
对Ω(g(n,m))和Θ(g(n,m))给出相应的定义。