一篇围绕算法分析的数学基础与渐进复杂度展开的学习笔记。


算法分析需要两套前置知识:用于推导求和、比较增长率的数学工具,以及用于量化资源消耗的渐进记号体系。本篇先整理前者(指数、对数、级数、模运算、证明方法),再展开后者(OΩΘo 的定义、运算法则与运行时间计算方法)。

1. 数学基础

1.1 指数

XAXB=XA+BXAXB=XAB(XA)B=XABXN+XN=2XNX2N2N+2N=2N+1

1.2 对数

在计算机科学中,若无特别声明,所有对数均以 2 为底。

定义XA=B 当且仅当 logXB=A

定理 1.1(换底公式):对任意 C>0

logAB=logCBlogCA

定理 1.2

logAB=logA+logB

常用等式

公式说明
logA/B=logAlogB
log(AB)=BlogA
logX<X对所有 X>0 成立
log1=0, log2=1
log1024=10, log1,048,576=20

1.3 级数

几何级数

i=0NAi=AN+11A1(A1)

0<A<1N 时,i=0Ai=11A

常用算术级数

i=1Ni=N(N+1)2N22i=1Ni2=N(N+1)(2N+1)6N33i=1NikNk+1|k+1|(k1)

调和级数

HN=i=1N1ilnN+γ

其中 γ0.57721566(欧拉常数)。HN=Θ(logN)

级数运算常用恒等式

i=1Nf(N)=Nf(N)i=n0Nf(i)=i=1Nf(i)i=1n01f(i)

1.4 模运算

N 整除 AB,则称 ABN 同余,记为 AB(modN)。直观上即 AB 除以 N 的余数相同。

性质示例
AB(modN),则 A+CB+C(modN)81611(mod10)
AB(modN),则 ADBD(modN)

1.5 证明方法

算法分析中最常使用的三种证明方法:

方法思路结构
归纳法证明最小情形(基准)成立,再假设 k 成立来证 k+1 成立基准情形 → 归纳假设 → 归纳步骤
反证法假设结论为假,推导出矛盾假设 ¬P → 推导 → 矛盾 → 故 P 成立
反例法举出一个不满足结论的具体实例直接给出反例即可推翻命题

归纳法示例——证明斐波那契数 Fi=Fi1+Fi2F0=1,F1=1)满足 Fi<(5/3)i

  • 基准F1=1<5/3F2=2<25/9
  • 归纳:假设对 i=1,2,,k 成立,则
Fk+1=Fk+Fk1<(5/3)k+(5/3)k1=(5/3)k1(5/3+1)<(5/3)k1(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>0n0,使得 Nn0T(N)cf(N)T(N) 增长率 ≤ f(N)(上界)
T(N)=Ω(g(N))Omega存在 c>0n0,使得 Nn0T(N)cg(N)T(N) 增长率 ≥ g(N)(下界)
T(N)=Θ(h(N))ThetaT(N)=O(h(N))T(N)=Ω(h(N))T(N) 增长率 = h(N)(紧界)
T(N)=o(p(N))小 olimNT(N)p(N)=0T(N) 增长率 < p(N)(严格上界)

注意:f(N)O(g(N)) 是错误的写法——不等式已隐含在 O 的定义中。同时,不要在 O 中保留常数和低阶项:O(2N2)O(N2+N) 都应写为 O(N2)

2.2 增长率比较

极限比较法:对于两个函数 f(N)g(N),计算 limNf(N)g(N)

极限值结论
0f(N)=o(g(N))
有限非零常数 cf(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)=max(O(f(N)),O(g(N)))T1(N)T2(N)=O(f(N)g(N))

法则 2:若 T(N)k 次多项式,则 T(N)=Θ(Nk)

法则 3:对任意常数 klogkN=O(N)

推论——复杂度分析中的简化原则:

原则说明
忽略常数因子O(1000N)=O(N)
忽略低阶项O(N2+N)=O(N2)
加法取最大循环并列时复杂度取各段最大者
乘法取乘积嵌套循环时复杂度取各层乘积

2.4 运行时间计算

分析运行时间的基本方法:逐语句累加运行时间,忽略常数,关注最内层循环的迭代次数。

a. 单层循环

c
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)+nO(1)+O(1)=O(N)

b. 嵌套循环

c
for (int i = 0; i < n; i++)         // n 次
    for (int j = 0; j < n; j++)     // n 次/每轮
        count++;                     // O(1)

总时间:nnO(1)=O(N2)

c. 依赖外层的循环

c
for (int i = 0; i < n; i++)         // n 次
    for (int j = 0; j < i; j++)     // i 次/每轮
        count++;
i=0n1i=n(n1)2=O(N2)

d. 对数时间

c
while (n > 1) {
    n /= 2;          // 每轮将 n 减半
    /* O(1) 操作 */
}

循环次数为 log2n,总时间 O(logN)

e. 递归函数

递归函数的时间复杂度通过递推关系式描述,判断复杂度的关键在于确定

  1. 递归层数 Nr
  2. i 层子任务数 ni (n1=1),i[1,Nr]
  3. 每层每个子任务的规模 Ti (往往并非常数,与层级有关)

则总复杂度可以表示如下:

T(N)=i=1Nr(niTi)

也就是将每层的总规模计算后,逐层累加。

情形 1:T(N)=T(N1)+O(1)

递归深度为 N,每层一个子问题,每个子问题规模为 O(1),因此

T(N)=NO(1)=O(N)

情形 2:T(N)=2T(N1)+O(1)

每个问题分为两个子问题,第 i 层有 2i 个子问题,每个子问题规模为 O(1),该层总工作量为 2iO(1) ,共 N 层,因此累加可得

i=1N2iO(1)=O(2N)

情形 3:T(N)=T(N/2)+O(1)

递归深度为 logN,每层一个子问题,每个子问题规模为 O(1),因此

T(N)=logNO(1)=O(logN)

情形 4:T(N)=2T(N/2)+O(N)

递归深度为 logN,第 i 层有 2i 个子问题,每个子问题规模为 O(N/2i),该层总工作量为 2iO(N/2i)=O(N) ,因此

T(N)=logNO(N)=O(NlogN)
递推式含义复杂度典型算法
T(N)=T(N1)+O(1)逐次减一 + 常数时间O(N)线性递归(如阶乘)
T(N)=2T(N1)+O(1)指数分支 + 常数时间O(2N)斐波那契朴素递归
T(N)=T(N/2)+O(1)减半 + 常数时间O(logN)二分查找
T(N)=2T(N/2)+O(N)两分 + 线性时间合并O(NlogN)归并排序

小结

主题核心内容
数学基础指数恒等式、对数换底公式、几何/算术/调和级数、模运算同余性质、归纳法与反证法
渐进记号O(上界)、Ω(下界)、Θ(紧界)、o(严格上界);用极限比较增长率的快慢
运算法则加法取最大、乘法取乘积、忽略常数和低阶项、多项式取最高次
运行时间计算逐句累加 → 关注最内层循环 → 查级数公式求和 → 递归问题检查每层工作量与分支数