Algorithm Design · Lecture 1-3

渐近记号:Big-OAsymptotic Notation: Big-O

上一课我们把 LinearSearch 数成了 T(n) = an + b。可那个 a、那个 b 到底是多少,我们既不知道、也不关心。这一课要造一把"尺子",把无关紧要的常数一刀切掉,只留下真正决定命运的东西——增长率 (growth rate)。这把尺子就是 Big-O。

✓ 对不对 (correctness) ✓ 怎么数 (T(n)) ▶ 怎么比 (Big-O)
Ke Chen · May 19, 2026 承接 Lecture 1-2 加详版 +50%
向下滚动开始 · scroll to begin
01

先建直觉:"输入放大 10 倍"测试 / The 10× test

上一课的结论是:放弃绝对速度,改看可扩展性 (scalability)。可"可扩展性"听着抽象,这里给你一个特别具体的抓手——

核心问题 · The one question 如果把输入规模放大到 10 倍(n → 10n),运行时间会变成几倍? 答案是"1 倍 / 10 倍 / 100 倍 / 还是直接爆炸",就决定了这个算法属于哪一档。

把常见的几种 T(n) 都代进去算一遍。点表格任意一行,右边会放大讲解:

档次 ClassT(n)T(10n)放大倍数
常数 constant42421× (不变)
线性 linear15n150n10×
二次 quadratic0.3n230n2100×
三次 cubic82n382000n31000×
指数 exponential1.5n1.510n ≈ 58n爆炸 💥
点上面任意一行查看推导
↑ 选一档

看规律:线性→10×、二次→100×、三次→1000×,而指数根本不服从"乘个固定倍数"的规矩——它是灾难级的。这就是为什么我们拼命想避开指数算法。

02

麻烦:an+b 并不"正好"10× / The puzzle

LinearSearch 的 T(n) = an + b 看着就是线性,理应落在"10×"那一档。可你真去代 10n,会发现它不精确

T(n) = an + b  →  T(10n) = a·(10n) + b = 10an + b
但"正好 10 倍"应该是 10·T(n) = 10·(an+b) = 10an + 10b

差在那个 b vs 10b。只要 b ≠ 0,T(10n) 就不等于 10·T(n)。拿具体数字看更扎心——设 a = 10ms、b = 7ms(还是老规矩,瞎编的常数):

T(10)  = 10·10 + 7 = 107
T(100) = 10·100 + 7 = 1007
如果是"正好 10 倍",应该得 10·107 = 1070,可实际是 1007。
于是矛盾来了 我们凭直觉说 an+b 是"线性、10× 那一档",可它代进去不是精确的 10 倍。难道它不算线性?—— 问题出在我们太较真于"精确"和"小 n"。下一节的修法既优雅又是本课的转折点。
03

修法:取极限,也就是"渐近" / Take the limit

别看某一个 n 的"精确倍数",看 n 趋于无穷时那个倍数逼近什么。这一步就是"渐近 (asymptotic)"的全部含义。

limn→∞ T(10n)T(n) = limn→∞ 10an + ban + b = 10

为什么极限是 10?分子分母同除 n:(10a + b/n) / (a + b/n),当 n→∞ 时 b/n → 0,剩下 10a / a = 10。那个碍事的 b 被无穷大"稀释"掉了。

互动 · 眼看着倍数逼近 10

T(n) = an + b。逐行加大 n,看比值 T(10n)/T(n) 怎么爬向 10。换个夸张的 b 试试——极限照样是 10:

选 (a, b): a=10, b=7 a=10, b=1000 a=3, b=500
nT(n)T(10n)T(10n)/T(n)
↑ 就算 b = 1000 一开始把比值压得很低,只要 n 够大,b 的影响被摊薄,比值照样收敛到 10。这就是"渐近"要表达的:大 n 时的行为,才是本质。
于是我们可以理直气壮地说 渐近地 (asymptotically),T(n) = an + b 和纯线性函数 15n 一个档次——输入 10×,时间约 10×。它们都远慢于二次函数 0.3n²(那个极限是 100)。"渐近"这个词,字面就是"趋于极限/趋于无穷时的行为"。
04

增长率赛跑:为什么可以无视小 n / Growth-rate race

"渐近"还带来一个解放:小 n 处的胜负无所谓。哪怕线性函数在起点领先,只要增长率更低,迟早会被超过。亲眼看看这场赛跑。

互动 · 五种增长率同场竞速
可见范围 n ≤ 60
关键观察 · The crossover 把范围拉到 60 以内看:橙线 15n 一开始高于绿线 0.3n²——在 n = 1 时甚至 15 > 0.3。但两条线在 n = 50 相交(都等于 750),此后二次永久反超。增长率讲的是"尾巴",不是"起跑"。所以渐近分析理直气壮地忽略小 n。
05

提炼成两条原则 / Two principles

前面所有直觉,浓缩成两句话。Big-O 的定义就是为了同时兑现这两句话而造的。

原则一:只看增长率 可以无视常数系数。15n、20n、an+b —— 前面那个数字是多少不重要,重要的是"它是 n 的几次方 / 什么形状"。
原则二:只管大 n 可以无视小 n 的噪声。前面有限个 n 上谁高谁低都无所谓,我们只关心"足够大之后"谁压过谁。

难点在于:怎么用一个数学上严格的定义,把"无视常数"和"无视小 n"这两件事同时说清楚?答案是——各配一个可以自由挑选的常数。往下看。

06

Big-O 的正式定义 / The definition

第一次读会觉得抽象——别急,我们把它拆成零件,再各配一个例子。

Definition · Big-O
对两个函数 f, g : ℕ → ℝ⁺,称 f(n) = O(g(n)),当且仅当
存在常数 c > 0n0 ∈ ℕ,使得
对所有 n ≥ n0,都有 f(n) ≤ c · g(n)
常数 c 是干嘛的?
它替你吃掉倍数。f 比 g 大个 15 倍没关系,挑个更大的 c 把它罩住即可 → 兑现"无视常数系数"。
门槛 n0 是干嘛的?
它替你跳过小 n。前面 n₀ 个点不等式不成立也没关系,只要求"从 n₀ 往后"成立 → 兑现"无视小 n 噪声"。

读法:f(n) = O(g(n)) 直觉上就是"f 的增长率不超过 g"(g 是 f 的一个上界档次)。注意这里的 "=" 是记号滥用——严格说应是 f ∈ O(g),O(g) 是"所有增长不快于 g 的函数"组成的一整个集合。这个坑第 9 节还会再提。

读定义的正确姿势 定义里有两个"存在"和一个"对所有"。证明 f = O(g) 时,c 和 n₀ 由你来挑(只要挑得出一组能用的就行),而不等式必须对挑定之后的所有大 n 成立。挑常数是你的自由,验证不等式是你的义务。
07

常数 c 的威力:吃掉倍数 / Example 1: 15n = O(n)

最能体现 c 作用的例子:证明 15n = O(n)。凭直觉它俩都是线性、显然同档;但没有 c 你根本写不出那个不等式。

目标:找 c, n₀ 使 15n ≤ c · n 对所有 n ≥ n₀ 成立。若不许用 c(即 c=1),15n ≤ n 永远不成立。但 c 可以随便挑——挑 c = 20:

15n ≤ 20·n 对所有 n ≥ 1 成立 → 15n = O(n)
互动 · Big-O 游乐场(例一:f = 15n,g = n)

拖动 c 和 n₀。蓝线是 f(n)=15n,紫线是 c·g(n)=c·n。目标:让紫线在阴影区(n ≥ n₀)里始终压住蓝线。

c =10
n0 =1
试试:c=10 时紫线永远压不住(因为 15>10);把 c 调到 15 以上,立刻就成立——n₀ 甚至可以留在 1。这就是"c 吃掉倍数"。
看规范的书面证明 · Write-up
命题 15n = O(n)。
c = 20,n₀ = 1。
验证 对任意 n ≥ 1:15n ≤ 20n = 20·g(n) = c·g(n)。不等式成立。
结论 存在 c=20, n₀=1 满足定义,故 15n = O(n)。∎
08

门槛 n₀ 的威力:跳过小 n / Example 2: n = O(n²−100)

这个例子专门凸显 n₀。证明 n = O(n² − 100)。麻烦在于那个 −100 会让小 n 处彻底翻车。

目标:找 c, n₀ 使 n ≤ c·(n² − 100) 对所有 n ≥ n₀ 成立。看小 n:当 n ≤ 10 时,n² − 100 ≤ 0(比如 n=1 时是 −99),右边是负数或零,而左边 n 是正数——不等式彻底反了,无论 c 取多大都救不了。

怎么办?别管小 n,把门槛往后挪。n₀ = 11、c = 1:此时 n² − 100 已经稳稳超过 n(例如 n=11:121−100=21 > 11)。于是:

n ≤ n² − 100 对所有 n > 11 成立 (取 c=1, n0=11) → n = O(n²−100)
互动 · Big-O 游乐场(例二:f = n,g = n²−100)

蓝线 f(n)=n,紫线 c·g(n)=c·(n²−100)。注意紫线在左边会钻到 0 以下——那正是小 n 翻车的地方。你的任务:把 n₀ 挪到翻车区右边。

c =1
n0 =1
试试:n₀=1 时,再大的 c 也没用(紫线在左端是负的);把 n₀ 拖到 11 及以后,即使 c=1 也立刻成立。这就是"n₀ 跳过小 n 噪声"。
看规范的书面证明 · Write-up
命题 n = O(n² − 100)。
c = 1,n₀ = 11。
验证 对任意 n ≥ 11,有 n² − 100 − n = n(n−1) − 100 ≥ 11·10 − 100 = 10 > 0,即 n ≤ n² − 100 = c·g(n)。
结论 存在 c=1, n₀=11 满足定义,故 n = O(n²−100)。∎
09

证明配方 + 常见陷阱 / Recipe & pitfalls

把两个例子的套路固化下来,以后遇到"证 f = O(g)"照做即可。

看倍数,定 c

比较 f 和 g 的最高次项系数,挑一个足够大的 c 把 f 的系数罩住。(例一里 15 → 挑 c=20)

看噪声,定 n₀

找出低次项/负常数会捣乱的小 n 范围,把 n₀ 设在这个范围之后。(例二里 −100 到 n=10 都在捣乱 → 挑 n₀=11)

验证不等式

写出对所有 n ≥ n₀ 都有 f(n) ≤ c·g(n) 的推导。给得出一组能用的 (c, n₀) 就算证完。

四个必须记住的陷阱 · Pitfalls

O 是上界,不是"相等档次"
15n = O(n²) 也对!

Big-O 只说"不快于"。所以线性函数也是 O(n²)、O(n³)——只是不够紧 (tight)。要说"恰好同档",那是 Θ 记号,以后再讲。

"=" 是记号滥用
别把它当等号做移项

f = O(g) 真正的意思是 f ∈ O(g)(集合成员)。所以能写 15n = O(n),但不能反过来写 O(n) = 15n。

常数与低次项照丢
O(3n²+5n+9) = O(n²)

渐近只看最高次项、且丢掉它的系数。8n²、0.3n²、1000n² 全是 O(n²)。

底数不同的指数不同档
O(2ⁿ) ≠ O(3ⁿ)

多项式里系数可丢,但指数的底数不能丢——2ⁿ 和 3ⁿ 不是一个量级。log 的底数倒是可丢(换底只差常数)。

建立全局图景:增长率阶梯 (部分超出本课,供参考)

从慢到快排好队。本课出现了常数、线性、二次、三次、指数;log 类这几课还没细讲,先混个脸熟,知道它们卡在哪就行。

O(1)常数 constant10× → 1×
O(log n)对数 logarithmic (本课未详述)几乎不涨
O(n)线性 linear —— LinearSearch 在这10× → 10×
O(n log n)线性对数 (排序常见,本课未详述)略超 10×
O(n²)二次 quadratic10× → 100×
O(n³)三次 cubic10× → 1000×
O(2ⁿ)指数 exponential —— 尽量躲开爆炸 💥
O(n!)阶乘 factorial —— 更是灾难爆炸 💥💥
10

随堂小测 / Quick Check

无限次尝试。这套题专挑定义里最容易栽的点。

Q1. "渐近 (asymptotic)"这个词,核心指的是?
Q2. 定义 f(n)=O(g(n)) 里,常数 c 的作用是?
Q3. 要证 n = O(n²−100),为什么单靠调大 c 不够,还必须挑 n₀?
Q4. 下面哪个说法是的?
Q5. T(n) = 3n² + 500n + 9999 的 Big-O 是?
Q6. 对 T(n)=an+b,极限 lim(n→∞) T(10n)/T(n) 等于?这说明它是?
Q7. 要证 f(n)=O(g(n)),你需要做的是?