一篇围绕算法分析的数学基础与渐进复杂度展开的学习笔记。
算法分析需要两套前置知识:用于推导求和、比较增长率的数学工具,以及用于量化资源消耗的渐进记号体系。本篇先整理前者(指数、对数、级数、模运算、证明方法),再展开后者(O、Ω、Θ、o
的定义、运算法则与运行时间计算方法)。
1. 数学基础
1.1 指数
XAXB=XA+B
XBXA=XA−B
(XA)B=XAB
XN+XN=2XN=X2N
2N+2N=2N+1
1.2 对数
在计算机科学中,若无特别声明,所有对数均以 2 为底。
定义:XA=B 当且仅当 logXB=A。
定理 1.1(换底公式):对任意 C>0,
logAB=logCAlogCB
定理 1.2:
logAB=logA+logB
常用等式:
| 公式 | 说明 |
|---|
| logA/B=logA−logB | |
| log(AB)=BlogA | |
| logX<X | 对所有 X>0 成立 |
| log1=0, log2=1 | |
| log1024=10, log1,048,576=20 | |
1.3 级数
几何级数:
i=0∑NAi=A−1AN+1−1(A=1)
当 0<A<1 且 N→∞ 时,i=0∑∞Ai=1−A1。
常用算术级数:
i=1∑Ni=2N(N+1)≈2N2
i=1∑Ni2=6N(N+1)(2N+1)≈3N3
i=1∑Nik≈∣k+1∣Nk+1(k=−1)
调和级数:
HN=i=1∑Ni1≈lnN+γ
其中 γ≈0.57721566(欧拉常数)。HN=Θ(logN)。
级数运算常用恒等式:
i=1∑Nf(N)=Nf(N)
i=n0∑Nf(i)=i=1∑Nf(i)−i=1∑n0−1f(i)
1.4 模运算
若 N 整除 A−B,则称 A 与 B 模 N 同余,记为 A≡B(modN)。直观上即 A 和 B 除以 N 的余数相同。
| 性质 | 示例 |
|---|
| 若 A≡B(modN),则 A+C≡B+C(modN) | 81≡61≡1(mod10) |
| 若 A≡B(modN),则 AD≡BD(modN) | |
1.5 证明方法
算法分析中最常使用的三种证明方法:
| 方法 | 思路 | 结构 |
|---|
| 归纳法 | 证明最小情形(基准)成立,再假设 k 成立来证 k+1 成立 | 基准情形 → 归纳假设 → 归纳步骤 |
| 反证法 | 假设结论为假,推导出矛盾 | 假设 ¬P → 推导 → 矛盾 → 故 P 成立 |
| 反例法 | 举出一个不满足结论的具体实例 | 直接给出反例即可推翻命题 |
归纳法示例——证明斐波那契数 Fi=Fi−1+Fi−2(F0=1,F1=1)满足 Fi<(5/3)i:
- 基准:F1=1<5/3,F2=2<25/9
- 归纳:假设对 i=1,2,…,k 成立,则
Fk+1=Fk+Fk−1<(5/3)k+(5/3)k−1=(5/3)k−1⋅(5/3+1)<(5/3)k−1⋅(5/3)2=(5/3)k+1
2. 复杂度分析
2.1 渐进记号
评估算法资源消耗时,比较的是函数的相对增长率(relative rate of growth),而非具体数值。渐进记号体系提供了一套正式框架。
定义:设 T(N) 和 f(N) 为定义在正整数上的函数。
| 记号 | 读法 | 定义 | 含义 |
|---|
| T(N)=O(f(N)) | 大 O | 存在 c>0 和 n0,使得 N≥n0 时 T(N)≤cf(N) | T(N) 增长率 ≤ f(N)(上界) |
| T(N)=Ω(g(N)) | Omega | 存在 c>0 和 n0,使得 N≥n0 时 T(N)≥cg(N) | T(N) 增长率 ≥ g(N)(下界) |
| T(N)=Θ(h(N)) | Theta | T(N)=O(h(N)) 且 T(N)=Ω(h(N)) | T(N) 增长率 = h(N)(紧界) |
| T(N)=o(p(N)) | 小 o | N→∞limp(N)T(N)=0 | T(N) 增长率 < p(N)(严格上界) |
注意:f(N)≤O(g(N)) 是错误的写法——不等式已隐含在 O 的定义中。同时,不要在 O 中保留常数和低阶项:O(2N2)
和 O(N2+N) 都应写为 O(N2)
2.2 增长率比较
极限比较法:对于两个函数 f(N) 和 g(N),计算 N→∞limg(N)f(N):
| 极限值 | 结论 |
|---|
| 0 | f(N)=o(g(N)) |
| 有限非零常数 c | f(N)=Θ(g(N)) |
| ∞ | g(N)=o(f(N)) |
查表法:
| 函数 | 名称 | 典型场景 |
|---|
| c | 常数 | 简单语句 |
| logN | 对数 | 二分查找 |
| log2N | 对数的平方 | |
| N | 平方根 | |
| N | 线性 | 顺序扫描 |
| NlogN | 线性对数 | 归并排序、堆排序 |
| N2 | 平方 | 冒泡排序、选择排序 |
| N3 | 立方 | 矩阵乘法(朴素) |
| 2N | 指数 | 穷举搜索 |
增长率由慢到快:
c<logN<log2N<N<N<NlogN<N2<N3<2N
值得注意的是:logN 增长极其缓慢——logkN=O(N) 对任意常数 k 成立。
2.3 运算法则
法则 1:若 T1(N)=O(f(N)) 且 T2(N)=O(g(N)),则
T1(N)+T2(N)T1(N)⋅T2(N)=max(O(f(N)),O(g(N)))=O(f(N)⋅g(N))
法则 2:若 T(N) 是 k 次多项式,则 T(N)=Θ(Nk)。
法则 3:对任意常数 k,logkN=O(N)。
推论——复杂度分析中的简化原则:
| 原则 | 说明 |
|---|
| 忽略常数因子 | O(1000N)=O(N) |
| 忽略低阶项 | O(N2+N)=O(N2) |
| 加法取最大 | 循环并列时复杂度取各段最大者 |
| 乘法取乘积 | 嵌套循环时复杂度取各层乘积 |
2.4 运行时间计算
分析运行时间的基本方法:逐语句累加运行时间,忽略常数,关注最内层循环的迭代次数。
a. 单层循环
c123456
int sum(int n) { int total = 0; // O(1) for (int i = 0; i < n; i++) // n 次迭代 total += i; // O(1) 每轮 return total; // O(1)}
总时间:O(1)+n⋅O(1)+O(1)=O(N)。
b. 嵌套循环
c123
for (int i = 0; i < n; i++) // n 次 for (int j = 0; j < n; j++) // n 次/每轮 count++; // O(1)
总时间:n⋅n⋅O(1)=O(N2)。
c. 依赖外层的循环
c123
for (int i = 0; i < n; i++) // n 次 for (int j = 0; j < i; j++) // i 次/每轮 count++;
i=0∑n−1i=2n(n−1)=O(N2)
d. 对数时间
c1234
while (n > 1) { n /= 2; // 每轮将 n 减半 /* O(1) 操作 */}
循环次数为 ⌈log2n⌉,总时间 O(logN)。
e. 递归函数
递归函数的时间复杂度通过递推关系式描述,判断复杂度的关键在于确定
- 递归层数 Nr
- 第 i 层子任务数 ni (n1=1),i∈[1,Nr]
- 每层每个子任务的规模 Ti (往往并非常数,与层级有关)
则总复杂度可以表示如下:
T(N)=i=1∑Nr(ni⋅Ti)
也就是将每层的总规模计算后,逐层累加。
情形 1:T(N)=T(N−1)+O(1)
递归深度为 N,每层一个子问题,每个子问题规模为 O(1),因此
T(N)=N⋅O(1)=O(N)
情形 2:T(N)=2T(N−1)+O(1)
每个问题分为两个子问题,第 i 层有 2i 个子问题,每个子问题规模为 O(1),该层总工作量为 2i⋅O(1)
,共 N 层,因此累加可得
i=1∑N2i⋅O(1)=O(2N)
情形 3:T(N)=T(N/2)+O(1)
递归深度为 logN,每层一个子问题,每个子问题规模为 O(1),因此
T(N)=logN⋅O(1)=O(logN)
情形 4:T(N)=2T(N/2)+O(N)
递归深度为 logN,第 i 层有 2i 个子问题,每个子问题规模为 O(N/2i),该层总工作量为 2i⋅O(N/2i)=O(N)
,因此
T(N)=logN⋅O(N)=O(NlogN)
| 递推式 | 含义 | 复杂度 | 典型算法 |
|---|
| T(N)=T(N−1)+O(1) | 逐次减一 + 常数时间 | O(N) | 线性递归(如阶乘) |
| T(N)=2T(N−1)+O(1) | 指数分支 + 常数时间 | O(2N) | 斐波那契朴素递归 |
| T(N)=T(N/2)+O(1) | 减半 + 常数时间 | O(logN) | 二分查找 |
| T(N)=2T(N/2)+O(N) | 两分 + 线性时间合并 | O(NlogN) | 归并排序 |
小结
| 主题 | 核心内容 |
|---|
| 数学基础 | 指数恒等式、对数换底公式、几何/算术/调和级数、模运算同余性质、归纳法与反证法 |
| 渐进记号 | O(上界)、Ω(下界)、Θ(紧界)、o(严格上界);用极限比较增长率的快慢 |
| 运算法则 | 加法取最大、乘法取乘积、忽略常数和低阶项、多项式取最高次 |
| 运行时间计算 | 逐句累加 → 关注最内层循环 → 查级数公式求和 → 递归问题检查每层工作量与分支数 |