Skip to content

Chapter 2 | Algorithm Analysis

criteria

  • Input
  • Output
  • Definiteness
  • Finiteness
  • Effectiveness

2 Asymptotic Notation

[Definition] T(N)=O(f(N)) if there are positive constants c and N0 such that T(N)cf(N) for all NN0

[Definition] T(N)=Ω(f(N)) if there are positive constants c and N0 such that T(N)cf(N) for all NN0

[Definition] T(N)=Θ(f(N)) if and only if T(N)=O(f(N)) and T(N)=Ω(f(N))

[Definition] T(N)=o(p(N)) if T(N)=O(p(N)) and T(N)Θ(p(N))

if T1(N)=O(f(N)) and T2(N)=O(g(N)) then

(a) T1(N)+T2(N)=max(O(f(N)),O(g(N)))

(b) T1(N)T2(N)=O(f(N)g(N))

if T(N) is a polynomial function, then T(N)=Θ(Nk)

中文刻画

🔑 四符号定义(基于标准 CLRS 表述)

符号数学定义直观含义关键特征
O(g(n))∃c>0, n₀: 0 ≤ f(n) ≤ c·g(n), ∀n≥n₀渐近上界“f 的增长不超过 g"(允许同阶)
Ω(g(n))∃c>0, n₀: 0 ≤ c·g(n) ≤ f(n), ∀n≥n₀渐近下界“f 的增长不低于 g"(允许同阶)
Θ(g(n))∃c₁,c₂>0, n₀: 0 ≤ c₁g(n) ≤ f(n) ≤ c₂g(n), ∀n≥n₀紧确界“f 与 g 同阶增长"(上下界同时成立)
o(g(n))∀c>0, ∃n₀: 0 ≤ f(n) < c·g(n), ∀n≥n₀严格上界“f 的增长严格慢于 g"(lim f/g = 0)

💡 记忆锚点

  • O / Ω / Θ:关注“存在某个常数 c"
  • o:要求“对任意小的 c 都成立” → 更强的上界
  • Θ = O ∩ Ω(同时满足上下界)

📌 经典案例验证

f(n)=(logn)100, g(n)=n0.01

  • f(n)=O(g(n)):存在 c=1 与足够大的 n0,使 f(n)cg(n)(渐近成立)
  • f(n)=o(g(n))limn(logn)100n0.01=0(对任意 c>0,终将小于 cg(n)
  • f(n)Ω(g(n)):不存在常数 c 使 f(n)cg(n) 对所有大 n 成立
  • f(n)Θ(g(n)):因不满足 Ω 条件

结论(logn)100=o(n0.01)O(n0.01),体现“对数多项式增长严格慢于任意正幂次多项式”。


⚠️ 常见误区澄清

误区正解
“O 仅表示最坏情况”O 是纯数学符号。算法中常以 O 描述最坏时间复杂度,但符号本身与“最好/最坏”无关
“n = O(n²) 是错的”正确。O 表示上界,n 的增长速率确实不超过 n2(如同“1万 ≤ 100万”为真)
“O 和 o 可互换”关键区别:• n=O(n) ✓(允许相等)• n=o(n) ✗(要求严格小于,极限非零)
“小 n 时 f > g,故 f ≠ O(g)"渐近分析仅关注 n 的趋势,小规模行为不影响结论

💎 实践建议

  • 描述算法复杂度:优先使用 Θ(若能证明紧确界),其次 O(最坏情况上界)
  • 强调主导关系:用 o 表明“被显著主导”(如 (logn)k=o(nε)
  • 证明问题下界:使用 Ω(如比较排序 Ω(nlogn)
  • 避免模糊表述:不说“大约 n2",而说“时间复杂度为 Θ(n2)"

时间复杂度计算

某分支提前终止

cpp
for (i=0; i < N*N/100; i++)      // 外层:i ∈ [0, N²/100)
    for (j = N; j > i; j--)      // 中层:仅当 i < N 时执行 (N - i) 次
        for (k=0; k < N*3; k++)  // 内层:固定 3N 次
            C += B;

Qwen-Max-Thinking

分析循环中“提前终止”(即内层循环因条件不满足而实际执行次数远低于声明范围)是算法复杂度分析的关键难点。核心原则:必须将内层循环的实际迭代次数表示为外层变量的函数,并通过求和精确计算总操作数,而非简单相乘循环上限。


🔑 分析步骤(以 Else 分支为例)

步骤 1️⃣:建立精确求和式

总操作数 =

i=0N2/1001[max(0, Ni)中层实际迭代×3N内层迭代]
步骤 2️⃣:拆分有效/无效区间(关键!)
  • 有效区间0i<N):
    max(0,Ni)=Ni>0,中层循环体执行i=0N1(Ni)3N=3NN(N+1)2=32N3+O(N2)
  • 无效区间Ni<N2/100):
    max(0,Ni)=0循环体不执行。仅剩:
    • 中层循环条件判断 1 次/外层迭代 → O(1)
    • 外层迭代次数 N2/100N=Θ(N2)
      → 总开销 Θ(N2)(低阶项)
步骤 3️⃣:合并并取主导项
总操作数=32N3+O(N2)有效区间+O(N2)无效区间=Θ(N3)

无效区间的 Θ(N2)Θ(N3) 主导,可忽略。


📌 通用分析框架(适用于所有“提前终止”场景)

步骤操作关键问题
1. 建模写出总操作数的求和表达式内层迭代次数如何依赖外层变量?(如 f(i)=max(0,Ni)
2. 拆分求解 f(i)>0 的区间何时循环体实际执行?解不等式(如 i<N
3. 分段求和分别计算有效/无效区间的贡献无效区间是否含非平凡开销?(如条件判断、函数调用)
4. 阶数比较保留最高阶项低阶项(如 Θ(N2) vs Θ(N3))是否可忽略?
5. 验证边界检查小 N 是否影响渐近结论渐近分析关注 N,小 N 不影响大 O

⚠️ 常见陷阱与应对

陷阱错误做法正确做法
盲目相乘循环上限(N2)×N×N=O(N4)分析内层实际迭代次数函数 f(i)
忽略无效区间开销假设“不执行=无开销”检查条件判断/初始化是否含高成本操作(本例中仅为 O(1)
误判有效区间范围认为 i0N2/100 全部有效严格解不等式:j>ijN 开始 → 仅 i<N 有效
混淆“循环声明次数”与“循环体执行次数”用外层循环次数代表总复杂度聚焦循环体内语句的实际执行总次数

💡 实用技巧

  1. 画小规模示例:取 N=5 手动计算各 i 对应的内层执行次数,快速发现规律。
  2. 用数学工具简化
    • 等差数列求和:i=0N1(Ni)=N(N+1)/2
    • 积分近似(大 N):i=0Nf(i)0Nf(x)dx
  3. 代码重写验证
    c
    // 等价转换:仅当 i < N 时进入中层循环
    for (i=0; i < min(N, N*N/100); i++) 
        for (j=i+1; j<=N; j++) 
            for (k=0; k<3*N; k++) ...
    直接暴露有效范围,复杂度一目了然。

核心心法:时间复杂度取决于算法实际执行的基本操作总数,而非循环语法的表面结构。遇到“提前终止”,立即问:“对每个外层索引 i,内层循环体究竟执行多少次?” —— 答案藏在循环条件与变量依赖关系中。掌握此思维,可破解绝大多数循环复杂度陷阱。