跳转至

第一章:算法分析

程序 = 数据结构 + 算法。算法分析衡量的是"当输入规模趋近无穷大时,程序表现的变化趋势"。


时间复杂度

Big-O 记号

Note

定义

\(T(N) = O(f(N))\) 当且仅当存在正常数 \(c\) 和 \(N_0\),使得对任意 \(N \ge N_0\),有 \(T(N) \le c \cdot f(N)\)。

\(O(f(N))\) 描述算法运行时间的上界,关注的是当 \(N \to \infty\) 时的增长趋势,忽略常数因子和低阶项。

增长率 记号 典型算法
常数 \(O(1)\) 数组下标访问
对数 \(O(\log N)\) 二分查找
线性 \(O(N)\) 顺序查找
线性对数 \(O(N\log N)\) 归并排序、堆排序
平方 \(O(N^2)\) 冒泡排序
立方 \(O(N^3)\) Floyd-Warshall
指数 \(O(2^N)\) 穷举搜索

Tip

Big-O 的常见误解

  • Big-O 是上界,不是严格上界。\(T(N)=N\) 也可以说 \(T(N)=O(N^2)\)
  • 只考虑最坏情况,不考平均复杂度(本课程)
  • \(T(N) = 3N^2 + 2N + 1\) 记作 \(O(N^2)\),常数和低阶项均忽略

常见代码结构的复杂度

简单循环

C++
1
2
3
4
5
6
for (int i = 0; i < N; i++)    // O(N)
    sum += a[i];

for (int i = 0; i < N; i++)    // O(N²)
    for (int j = 0; j < N; j++)
        sum += a[i][j];

条件语句

C++
1
2
3
4
if (condition)
    fun1();  // O(N)
else
    fun2();  // O(N²)
取较大的分支:\(O(N^2)\)。

顺序执行

C++
for (...) {}   // O(N)
for (...) {}   // O(N²)
取最慢的部分:\(O(N^2)\)。忽略前面的 \(O(N)\)。

Warning

嵌套循环不等于 \(O(N^2)\)

C++
1
2
3
for (int i = 1; i <= N; i *= 2)       // O(log N)
    for (int j = 1; j <= i; j++)      // 内层执行 1+2+4+...+N ≈ 2N
        count++;
总复杂度是 \(O(N)\),不是 \(O(N\log N)\)。外层循环 \(\log N\) 次,内层总共执行约 \(2N\) 次。


递归算法分析

主定理 (Master Theorem)

对形如 \(T(N) = a \cdot T(N/b) + f(N)\) 的递推式(\(a\ge 1, b>1\)):

情形 \(f(N)\) 比 \(N^{\log_b a}\) 结论
1 增长更慢:\(O(N^{\log_b a - \varepsilon})\) \(T(N) = \Theta(N^{\log_b a})\)
2 同阶:\(\Theta(N^{\log_b a})\) \(T(N) = \Theta(N^{\log_b a}\log N)\)
3 增长更快且满足正则条件 \(T(N) = \Theta(f(N))\)

典型例子

递推式 \(a,b\) \(N^{\log_b a}\) \(f(N)\) 结果
\(T(N)=2T(N/2)+N\) \(2,2\) \(N\) \(N\) \(\Theta(N\log N)\)
\(T(N)=T(2N/3)+1\) \(1,\frac{3}{2}\) \(1\) \(1\) \(\Theta(\log N)\)
\(T(N)=3T(N/2)+N^2\) \(3,2\) \(N^{1.585}\) \(N^2\) \(\Theta(N^2)\)
\(T(N)=T(N/2)+N\) \(1,2\) \(1\) \(N\) \(\Theta(N)\)

Tip

什么时候不能套主定理?

\(T(N) = T(N-1) + 1\) 不是分治形式,\(b\) 不存在,需用数学归纳法或展开求解。


数学归纳法

用于证明递推式的解或算法性质。

三步走: 1. Base Case:证明 \(N\) 取初值时命题成立 2. 归纳假设:假设对所有 \(k < N\) 命题成立 3. 归纳步:利用假设证明 \(N\) 时命题成立

例题:证明 \(T(N) = T(N-1) + 1\)(\(T(1)=1\))的解为 \(T(N)=N\)。

  • Base:\(T(1)=1\),成立
  • 假设 \(T(N-1) = N-1\)
  • 则 \(T(N) = T(N-1) + 1 = (N-1) + 1 = N\)

例题:证明 \(T(N) = 2T(N/2) + N\) 的解为 \(T(N) = N\log N + N\)(\(N\) 为 2 的幂)。

  • Base:\(N=1\) 时 \(T(1)=1\),\(1\cdot\log 1+1=1\),成立
  • 假设 \(T(N/2) = \frac{N}{2}\log\frac{N}{2} + \frac{N}{2}\)
  • 则 \(T(N) = 2\left(\frac{N}{2}\log\frac{N}{2} + \frac{N}{2}\right) + N = N(\log N - 1) + N + N = N\log N + N\)

空间复杂度

衡量算法执行所需的额外存储空间(不含输入数据本身)。

  • 原地算法 (In-place):仅需 \(O(1)\) 额外空间(如插入排序、堆排序)
  • 非原地算法:需要额外数组/递归栈(如归并排序需 \(O(N)\))

复杂度速查

数据结构/算法 最坏时间 额外空间
数组
随机访问 \(O(1)\) —
线性查找 \(O(N)\) \(O(1)\)
二分查找(有序) \(O(\log N)\) \(O(1)\)
链表
头插入/删除 \(O(1)\) \(O(1)\)
查找/中间插入 \(O(N)\) \(O(1)\)
栈/队列
push / pop \(O(1)\) \(O(1)\)
BST
插入/查找/删除 \(O(N)\) \(O(N)\)
AVL
插入/查找/删除 \(O(\log N)\) \(O(N)\)
二叉堆
插入/deleteMin \(O(\log N)\) \(O(1)\)
buildHeap (Floyd) \(O(N)\) \(O(1)\)
排序
插入排序 \(O(N^2)\) \(O(1)\)
希尔排序 \(O(N^{3/2})\) \(O(1)\)
堆排序 \(O(N\log N)\) \(O(1)\)
归并排序 \(O(N\log N)\) \(O(N)\)
快速排序 \(O(N^2)\) \(O(\log N)\)