Chapter 2 | Algorithm Analysis
criteria
- Input
- Output
- Definiteness
- Finiteness
- Effectiveness
2 Asymptotic Notation
[Definition]
[Definition]
[Definition]
[Definition]
if
(a)
(b)
if
中文刻画
🔑 四符号定义(基于标准 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 ∩ Ω(同时满足上下界)
📌 经典案例验证
设
- ✅
:存在 与足够大的 ,使 (渐近成立) - ✅
: (对任意 ,终将小于 ) - ❌
:不存在常数 使 对所有大 成立 - ❌
:因不满足 条件
→ 结论:
⚠️ 常见误区澄清
| 误区 | 正解 |
|---|---|
| “O 仅表示最坏情况” | O 是纯数学符号。算法中常以 O 描述最坏时间复杂度,但符号本身与“最好/最坏”无关 |
| “n = O(n²) 是错的” | 正确。O 表示上界, |
| “O 和 o 可互换” | 关键区别:• |
| “小 n 时 f > g,故 f ≠ O(g)" | 渐近分析仅关注 |
💎 实践建议
- 描述算法复杂度:优先使用 Θ(若能证明紧确界),其次 O(最坏情况上界)
- 强调主导关系:用 o 表明“被显著主导”(如
) - 证明问题下界:使用 Ω(如比较排序
) - 避免模糊表述:不说“大约
",而说“时间复杂度为 "
时间复杂度计算
某分支提前终止
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️⃣:建立精确求和式
总操作数 =
步骤 2️⃣:拆分有效/无效区间(关键!)
- 有效区间(
): ,中层循环体执行 - 无效区间(
): ,循环体不执行。仅剩: - 中层循环条件判断 1 次/外层迭代 →
- 外层迭代次数
→ 总开销(低阶项)
- 中层循环条件判断 1 次/外层迭代 →
步骤 3️⃣:合并并取主导项
无效区间的
📌 通用分析框架(适用于所有“提前终止”场景)
| 步骤 | 操作 | 关键问题 |
|---|---|---|
| 1. 建模 | 写出总操作数的求和表达式 | 内层迭代次数如何依赖外层变量?(如 |
| 2. 拆分 | 求解 | 何时循环体实际执行?解不等式(如 |
| 3. 分段求和 | 分别计算有效/无效区间的贡献 | 无效区间是否含非平凡开销?(如条件判断、函数调用) |
| 4. 阶数比较 | 保留最高阶项 | 低阶项(如 |
| 5. 验证边界 | 检查小 | 渐近分析关注 |
⚠️ 常见陷阱与应对
| 陷阱 | 错误做法 | 正确做法 |
|---|---|---|
| 盲目相乘循环上限 | 分析内层实际迭代次数函数 | |
| 忽略无效区间开销 | 假设“不执行=无开销” | 检查条件判断/初始化是否含高成本操作(本例中仅为 |
| 误判有效区间范围 | 认为 | 严格解不等式: |
| 混淆“循环声明次数”与“循环体执行次数” | 用外层循环次数代表总复杂度 | 聚焦循环体内语句的实际执行总次数 |
💡 实用技巧
- 画小规模示例:取
手动计算各 对应的内层执行次数,快速发现规律。 - 用数学工具简化:
- 等差数列求和:
- 积分近似(大
):
- 等差数列求和:
- 代码重写验证: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++) ...
✅ 核心心法:时间复杂度取决于算法实际执行的基本操作总数,而非循环语法的表面结构。遇到“提前终止”,立即问:“对每个外层索引
,内层循环体究竟执行多少次?” —— 答案藏在循环条件与变量依赖关系中。掌握此思维,可破解绝大多数循环复杂度陷阱。