第一章:算法分析¶
程序 = 数据结构 + 算法。算法分析衡量的是"当输入规模趋近无穷大时,程序表现的变化趋势"。
时间复杂度¶
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++ | |
|---|---|
条件语句
取较大的分支:\(O(N^2)\)。顺序执行
取最慢的部分:\(O(N^2)\)。忽略前面的 \(O(N)\)。Warning
嵌套循环不等于 \(O(N^2)\)
| C++ | |
|---|---|
递归算法分析¶
主定理 (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)\) |