斐波那契数列
评价一个算法,始终围绕三个问题:它正确吗?它跑多快(作为输入规模 n 的函数)?我们能不能做得更好?本讲用斐波那契数列贯穿始终,回答这三个问题。其形式化定义:
斐波那契在《计算之书》(Liber Abaci)中推动了十进制记数法在欧洲的传播;讲义随后用以他命名的数列比较不同算法的效率。
Fn=⎩⎨⎧01Fn−1+Fn−2n=0n=1n>1
指数时间算法 FIBO1:照定义递归实现。
算法:FIBO1(n)输入: 自然数 nif n=0 then return(0)if n=1 then return(1)return FIBO1(n−1)+FIBO1(n−2)
FIBO1 把定义直接翻译成递归——Fn 分解为 Fn−1 与 Fn−2 两个子问题分别求解再相加,正确性显然。但每次求 Fn 都要重新计算两个子问题,子问题又被更深层递归重复计算,形成海量冗余。设 T(n) 为 FIBO1(n) 的计算机步数:
T(n)=O(1) (n≤1),T(n)=T(n−1)+T(n−2)+3 (n>1)
递推式与斐波那契同构,可归纳证明 T(n)≥Fn,故 T(n) 随 n 指数增长。
例如 F6=8,但 FIBO1(6) 会重复计算 FIBO1(4)、FIBO1(3) 等子问题。讲义提出的 F100 和 F200 分别为 354224848179261915075 和 280571172992510140037611932413038677189525;前者有 69 位,后者有 138 位。结果本身并不长,耗时主要来自重复递归。
指数增长的代价仍然惊人。F200=280571172992510140037611932413038677189525≈2.81×1041,介于 2137 与 2138 之间。按讲义采用的 2010 年“天河一号”约 2.57×1015 步/秒估算,仅执行 F200 次基本操作就至少需要约 1.1×1026 秒,即 3.5×1018 年。课件在这一页给出的 2138 与十进制近似值不一致,笔记采用精确数值重新估算。按摩尔定律硬件每 18 个月翻番,而 FIBO1 耗时正比于 20.694n≈1.6n:算 Fn+1 比 Fn 约慢 1.6 倍,即硬件每年约只能多承担一个输入下标。这是指数时间的代价。
多项式时间算法 FIBO2:自底向上填表,消除重复计算。
算法:FIBO2(n)输入: 自然数 nif n=0 then return(0)创建数组 f[0…n]f[0]=0; f[1]=1for i=2 to n dof[i]=f[i−1]+f[i−2]return f[n]
FIBO2 按 i=2,3,…,n 依次计算 f[i],每个值只依赖前两项且只算一次,消除了指数级重复。内层循环执行 n−1 次,故步数关于 n 线性,从指数级降至多项式级。
更细致地看:若加法为常数时间(32 位整数),FIBO2 为 O(n)。但第 n 个斐波那契数约 0.694n 位,大数加法需正比于位数的代价(见第二讲)。任意精度加法下,第 i 轮花 O(i) 位操作,故 FIBO2 实际为 ∑i=1nO(i)=O(n2),仍为多项式。FIBO1 的加法次数已是指数级;即使用 O(n) 作为每次加法的统一上界,总时间仍至多为 O(nFn),并至少与调用次数同阶。
渐近记号
渐近记号刻画函数的增长趋势,忽略常数因子与低阶项。
大 O 记号(上界):
定义(大 O):f(n) 是 O(g(n)),若存在常数 c>0 与 n0≥0,使对所有 n≥n0 有 0≤f(n)≤c⋅g(n)。
O(g(n)) 是"增长不超过 g(n)"的函数集合;c 负责放大常数,n0 决定从足够大处观察。例:f(n)=32n2+17n+1 是 O(n2)(取 c=50,n0=1),但不是 O(n) 也不是 O(nlogn)。典型用法:插入排序需 O(n2) 次比较。
Quiz:f(n)=3n2+17nlogn+1000 是 O(n2)、O(n3),还是两者都是?
两者都是。因为 nlogn=O(n2),整个式子是 O(n2);又因 n2=O(n3),它也是 O(n3)。其中 O(n2) 给出的上界更紧。
记号滥用:严格地 f(n)∈O(g(n)),但习惯写 f(n)=O(g(n))。等号是单向的——g1=5n3、g2=3n2 均有 g1=O(n3)、g2=O(n3),却不能推出 g1=g2。
大 O 记号的性质:自反性(f=O(f))、常数因子(f=O(g)⇒cf=O(g))、乘积(f1=O(g1),f2=O(g2)⇒f1f2=O(g1g2))、求和(f1+f2=O(max{g1,g2}))、传递性(f=O(g),g=O(h)⇒f=O(h))。
乘积性质的证明:由定义存在 c1,n1 使 0≤f1(n)≤c1g1(n)(n≥n1),存在 c2,n2 使 0≤f2(n)≤c2g2(n)(n≥n2)。两式相乘得 0≤f1f2≤c1c2g1g2(n≥max{n1,n2}),恰为乘积定义。由此 f(n)=5n3+3n2+n+1234 是 O(n3)——最高次项主导。
大 Ω 记号(下界):
定义(大 Ω):f(n) 是 Ω(g(n)),若存在常数 c>0 与 n0≥0,使对所有 n≥n0 有 f(n)≥c⋅g(n)≥0。
Ω 与 O 方向相反,等价关系:f=Ω(g) 当且仅当 g=O(f)。例:f(n)=32n2+17n+1 既是 Ω(n2) 也是 Ω(n),但不是 Ω(n3)。典型用法:任何基于比较的排序算法最坏情况至少需 Ω(nlogn) 次比较。
Quiz:f=Ω(g) 是否等价于 g=O(f)?若 f(n)≥cg(n) 只对无穷多个 n 成立,是否足够?
第一种说法成立,把不等式两边交换即可得到相同的定义。第二种说法不成立:Ω 要求从某个 n0 开始对所有更大的 n 成立。例如令 g(n)=n,f(n) 在偶数 n 时为 n、奇数 n 时为 1;前者对无穷多个 n 成立,但 f∈/Ω(g)。
大 Θ 记号(紧界):
定义(大 Θ):f(n) 是 Θ(g(n)),若存在常数 c1>0,c2>0 与 n0≥0,使对所有 n≥n0 有 0≤c1g(n)≤f(n)≤c2g(n)。
Θ 同时夹紧上下界,等价于 f=O(g) 且 f=Ω(g)。例:f(n)=32n2+17n+1 是 Θ(n2),但不是 Θ(n3) 也不是 Θ(n)。典型用法:归并排序需 Θ(nlogn) 次比较。
Quiz:f=Θ(g) 是否等价于 f 同时是 O(g) 和 Ω(g)?是否等价于 f(n)/g(n) 收敛到一个正的有限常数?
第一种说法成立。第二种只给出充分条件,并非必要条件:取 g(n)=n,f(n)=n(2+(−1)n),则 n≤f(n)≤3n,所以 f=Θ(g);但比值在 1 与 3 之间交替,没有极限。
渐近界与极限:极限是判断渐近关系的捷径。
命题:若 limn→∞g(n)f(n)=c 且 0<c<∞,则 f(n) 是 Θ(g(n))。
证明:由极限定义,对任意 ε>0,存在 n0 使 c−ε≤g(n)f(n)≤c+ε(n≥n0)。取 ε=2c>0,两边乘 g(n) 得 2cg(n)≤f(n)≤23cg(n),正是 Θ 定义(c1=2c、c2=23c)。思路是"夹逼":极限收敛到 c,就把比值锁在 c 附近,从而把 f 夹在两个常数倍的 g 之间。
补充命题:若极限为 0,则 f=O(g) 但非 Ω(g);若极限为 ∞,则 f=Ω(g) 但非 O(g)。
常见函数的渐近界
利用极限判别,可得常见函数的渐近界:
- 多项式:f(n)=a0+a1n+⋯+adnd(ad>0)是 Θ(nd),因 limn→∞ndf(n)=ad>0。
- 对数与多项式:任意 a>1,d>0,logan 是 O(nd),因 limn→∞ndlogan=0。
- 指数与多项式:任意 r>1,d>0,nd 是 O(rn),因 limn→∞rnnd=0。
- 阶乘:n! 是 2Θ(nlogn),由斯特林公式 n!∼2πn(en)n 可证。
增长快慢层次(慢→快):logn≺nd(d>0)≺rn(r>1)≺n!。
练习 0.1:对下列每组函数,判断 f=O(g)、f=Ω(g),还是两者都成立(即 f=Θ(g))。
考查渐近记号的极限判别,以及对数、多项式、指数和阶乘的增长速度。以下均令 n→∞;f/g 趋于 0、正的有限常数或 ∞ 时,分别得到 O(g)、Θ(g) 或 Ω(g)。
(a) f(n)=n−100,g(n)=n−200。
g(n)f(n)=1−200/n1−100/n→1,故 f=Θ(g)。
(b) f(n)=n1/2,g(n)=n2/3。
g(n)f(n)=n−1/6→0,故 f=O(g)。
© f(n)=100n+logn,g(n)=n+(logn)2。
两边主项都是线性项,g(n)f(n)→100,故 f=Θ(g)。
(d) f(n)=nlogn,g(n)=10nlog(10n)。
由 log(10n)=logn+log10,有 g(n)f(n)→1/10,故 f=Θ(g)。
(e) f(n)=log(2n),g(n)=log(3n)。
g(n)f(n)=logn+log3logn+log2→1,故 f=Θ(g)。
(f) f(n)=10logn,g(n)=log(n2)。
g(n)=2logn,故 g(n)f(n)=5,即 f=Θ(g)。
(g) f(n)=n1.01,g(n)=n(logn)2。
g(n)f(n)=(logn)2n0.01→∞,故 f=Ω(g)。
(h) f(n)=n2/logn,g(n)=n(logn)2。
g(n)f(n)=(logn)3n→∞,故 f=Ω(g)。
(i) f(n)=n0.1,g(n)=(logn)10。
正数次幂快于任意固定的对数幂,g(n)f(n)→∞,故 f=Ω(g)。
(j) f(n)=(logn)logn,g(n)=n/logn。
ln(f/g)=(logn)lnlogn−lnn+lnlogn→∞,故 f=Ω(g)。
(k) f(n)=n,g(n)=(logn)3。
g(n)f(n)→∞,故 f=Ω(g)。
(l) f(n)=n1/2,g(n)=5log2n。
g(n)=nlog25 且 log25>1/2,故 f=O(g)。
(m) f(n)=n2n,g(n)=3n。
g(n)f(n)=n(2/3)n→0,故 f=O(g)。
(n) f(n)=2n,g(n)=2n+1。
g(n)f(n)=1/2,故 f=Θ(g)。
(o) f(n)=n!,g(n)=2n。
n!≥(n/2)⌊n/2⌋,所以 g(n)f(n)→∞,故 f=Ω(g)。
(p) f(n)=(logn)logn,g(n)=2(log2n)2。
lnf=Θ(lognloglogn),而 lng=Θ((logn)2);故 f/g→0,即 f=O(g)。
(q) f(n)=∑i=1nik,g(n)=nk+1,其中 k 是固定的非负整数。
由积分比较,
k+1nk+1≤i=1∑nik≤k+1(n+1)k+1−1.
两端除以 nk+1 得 g(n)f(n)→k+11,故 f=Θ(g)。
练习 0.2:设 c>0,证明几何和 g(n)=1+c+c2+⋯+cn 分别满足以下渐近界。
考查等比数列求和与渐近上下界。
(a) 当 c<1 时,证明 g(n)=Θ(1)。
1≤g(n)=1−c1−cn+1≤1−c1,故 g(n)=Θ(1)。
(b) 当 c=1 时,证明 g(n)=Θ(n)。
g(n)=n+1=Θ(n)。
© 当 c>1 时,证明 g(n)=Θ(cn)。
cn≤g(n)=c−1cn+1−1≤c−1ccn,故 g(n)=Θ(cn)。
多变量大 O:算法常有多个输入参数,如节点数 n 与边数 m。
定义(多变量大 O):f(m,n) 是 O(g(m,n)),若存在常数 c>0,m0≥0,n0≥0,使对所有 n≥n0、m≥m0 有 f(m,n)≤c⋅g(m,n)。
例:f(m,n)=32mn2+17mn+32n3 是 O(mn2+n3) 与 O(mn3),但不是 O(n3) 也不是 O(mn2)。典型用法:BFS 在 n 节点、m 边的有向图求最短路径耗时 O(m+n)。