算法设计基础考点总结
算法设计基础考点总结
教材:《算法设计与分析》
适用范围:计算机专业算法课程期末复习
说明:⭐ 标注重要性等级(⭐⭐⭐⭐⭐ 最高)
📘 第1章 算法问题求解基础 ⭐⭐⭐
1. 算法的定义与特征 ⭐⭐⭐⭐⭐
算法(Algorithm) 是求解问题的一系列有限步骤。通俗地说,算法就是解决问题的精确方法——它用清晰无歧义的指令序列,告诉你第一步做什么、第二步做什么,直到最终得出结果。
算法 ≠ 程序:算法是设计层面的逻辑步骤,程序是算法在计算机上的具体实现。
算法必须具备以下5个核心特征:
| 特征 | 英文 | 说明 | 反例 |
|---|---|---|---|
| 输入 | Input | 有0个或多个外部输入量 | 随机数生成器可以无输入 |
| 输出 | Output | 至少产生一个输出量 | 死循环没有输出,不是算法 |
| 确定性 | Definiteness | 每条指令清晰、无歧义 | “选择合适的数据结构”不是确定指令 |
| 有限性 | Finiteness | 对任何合法输入,必须在有限步内结束 | 操作系统虽然不终止,但不属于算法范畴 |
| 可行性 | Effectiveness | 每条指令都足够基本,可以执行 | “列出所有实数”不可行 |
考试提示:5个特征必须全部记住,尤其区分”确定性”(无歧义)和”有限性”(一定终止于有限步)。
2. 算法设计的基本过程 ⭐⭐⭐
每一步的要点:
- 理解问题:不仅要读懂题意,还要明确约束条件、输入范围、输出格式
- 选择策略:这是最核心的决策——判断问题适合用分治、贪心、DP还是回溯
- 设计算法:先用伪代码描述逻辑,不要急着写具体语言
- 证明正确性:贪心法必须证明局部最优导致全局最优;归纳法证明递归算法的正确性
- 分析复杂度:分别分析最坏、平均情况的时间复杂度和空间复杂度
- 编写代码:注意边界条件、溢出、空指针等细节
3. 递归与归纳证明 ⭐⭐⭐⭐
递归是算法设计中最重要的思维工具之一。其核心思想是:将大问题分解为同类型的、规模更小的子问题。
递归的两个核心要素:
- 递归出口(基值条件 / Base Case):使递归终止的最简单情况,必须有!
- 递归体(Recursive Case):将原问题化简为规模更小的同类子问题
1 | 递归设计三步走: |
例1:汉诺塔问题(经典递归案例)
问题:三根柱子 A、B、C,n 个大小不同的盘在 A 柱上,每次只能移动一个盘,大盘不能放在小盘上面,求将所有盘移到 C 柱的步骤。
1 | 分析思路: |
1 | void Hanoi(int n, char A, char B, char C) { |
时间复杂度推导:
$$T(n) = 2T(n-1) + 1,\quad T(1)=1$$
展开得:$T(n) = 2^n - 1 = O(2^n)$
例2:斐波那契数列
1 | // 指数级递归(大量重复计算,n=50就几乎跑不动) |
考试提示:斐波那契递归的重复计算问题是引出动态规划思想的经典案例,期末考试经常以此作为对比。
4. 数学归纳法证明 ⭐⭐⭐
归纳法模板(用于证明递归算法的正确性):
1 | ① 归纳基础:验证 n 取最小值时结论成立 |
例:证明汉诺塔算法的最少移动次数为 2^n - 1
- **基础 (n=1)**:移动 1 次 = 2¹-1 = 1,✅ 成立
- 假设:n-1 个盘需要 2^(n-1) - 1 次移动
- 步骤:
- n-1 个盘 A→B:需要 2^(n-1) - 1 次
- 最大盘 A→C:需要 1 次
- n-1 个盘 B→C:需要 2^(n-1) - 1 次
- 总计 = (2^(n-1) - 1) + 1 + (2^(n-1) - 1) = 2^n - 1 ✅
📗 第2章 算法分析基础 ⭐⭐⭐⭐
1. 渐近表示法 ⭐⭐⭐⭐⭐
为什么需要渐近表示法?
算法的实际运行时间取决于机器速度、编译器优化、数据规模等。我们无法精确测量,所以需要用与机器无关的方法来描述算法的效率增长趋势。这就是渐近分析。
渐近表示法关系图:
1 | 增长趋势 |
四种渐近记号对比:
| 记号 | 含义 | 关键不等式 | 通俗理解 | 类比 |
|---|---|---|---|---|
| 大O $O(g(n))$ | 渐进上界 | $f(n) \le c \cdot g(n)$ | $f$ 增长不超过 $g$ | $f$ ≤ $g$ |
| 大Ω $\Omega(g(n))$ | 渐进下界 | $f(n) \ge c \cdot g(n)$ | $f$ 增长至少 $g$ 那么快 | $f$ ≥ $g$ |
| 大Θ $\Theta(g(n))$ | 渐进紧确界 | $c_1g(n) \le f(n) \le c_2g(n)$ | $f$ 和 $g$ 增长同级别 | $f$ = $g$ |
| 小o $o(g(n))$ | 非紧上界 | $\lim_{n \to \infty} \frac{f(n)}{g(n)} = 0$ | $f$ 增长严格慢于 $g$ | $f$ < $g$ |
大O证明三步法:
- 找出候选的 $c$ 和 $n_0$
- 验证当 $n \ge n_0$ 时,不等式 $f(n) \le c \cdot g(n)$ 成立
- 写结论
例1:证明 $3n^2 + 2n + 1 = O(n^2)$
1 | 解法:需要找到 c 和 n₀,使得 n ≥ n₀ 时 3n²+2n+1 ≤ c·n² |
例2:证明 $n^3 \neq O(n^2)$(反证法)
1 | 假设 n³ = O(n²),则存在 c > 0, n₀ 使 n³ ≤ c·n²(∀ n ≥ n₀) |
考试提示:大O证明是必考题!关键技巧是把低阶项”放大”:如 $an^k + bn^{k-1} + … ≤ (|a|+|b|+…)·n^k$(当 $n \ge 1$)。
2. 时间复杂度增长曲线对比 ⭐⭐⭐⭐
1 | n=1 n=10 n=100 n=1000 n=10000 |
增长速率排名(从慢到快):
1 | O(1) < O(log n) < O(√n) < O(n) < O(n log n) < O(n²) < O(n³) < O(2ⁿ) < O(n!) < O(nⁿ) |
实用结论:$n \le 10^6$ 时 $O(n\log n)$ 秒级可运行,$n \le 10^4$ 时 $O(n^2)$ 勉强可以,$O(2^n)$ 在 $n>30$ 时基本不可行。
3. 递推关系与主方法 ⭐⭐⭐⭐⭐
常见递推式及直觉得解:
| 类型 | 递推式 | 解 | 直观理解 |
|---|---|---|---|
| 线性减一 | $T(n) = T(n-1) + c$ | $O(n)$ | 每次减1,共n层 |
| 减半 | $T(n) = T(n/2) + c$ | $O(\log n)$ | 每次减半,共log n层 |
| 分治(均衡) | $T(n) = 2T(n/2) + O(n)$ | $O(n\log n)$ | 每层O(n),共log n层 |
| 分治(一般) | $T(n) = aT(n/b) + f(n)$ | 用主方法 | 取决于a,b,f(n) |
主方法(Master Theorem) ⭐⭐⭐⭐⭐
适用范围:$T(n) = aT(n/b) + f(n)$,其中 $a \ge 1, b > 1$
核心思想:比较 $f(n)$(分解+合并代价)与 $n^{\log_b a}$(叶结点代价)
1 | 判据:令 E = log_b(a),即 n^E = n^{log_b a} |
| 情况 | 条件 | 结论 | 本质 |
|---|---|---|---|
| 情况1:$f(n)$ 更小 | $f(n) = O(n^{E-\varepsilon})$ | $T(n) = \Theta(n^E)$ | 叶结点成本主导 |
| 情况2:两者相当 | $f(n) = \Theta(n^E)$ | $T(n) = \Theta(n^E \log n)$ | 每层均衡 |
| 情况3:$f(n)$ 更大 | $f(n) = \Omega(n^{E+\varepsilon})$ 且 $af(n/b) \le cf(n)$ | $T(n) = \Theta(f(n))$ | 分解合并成本主导 |
例题精讲:
| 题目 | a | b | $E=\log_b a$ | $f(n)$ | 判断 | 答案 |
|---|---|---|---|---|---|---|
| $T(n)=9T(n/3)+n$ | 9 | 3 | $\log_3 9=2$ | $n = O(n^{2-1})$ | 情况1 | $\Theta(n^2)$ |
| $T(n)=T(2n/3)+1$ | 1 | 3/2 | $\log_{3/2}1=0$ | $1 = \Theta(n^0)$ | 情况2 | $\Theta(\log n)$ |
| $T(n)=3T(n/4)+n\log n$ | 3 | 4 | $\log_4 3\approx0.79$ | $n\log n = \Omega(n^{0.79+0.2})$ | 情况3 | $\Theta(n\log n)$ |
| $T(n)=2T(n/2)+n\log n$ | 2 | 2 | $\log_2 2=1$ | $n\log n \neq \Theta(n^1)$ | 主方法不适用 | 递归树法 |
考试提示:
- 主方法前先算 $\log_b a$,写了就给分
- 找出 $\varepsilon$(哪怕 $\varepsilon=0.01$ 也算数)
- 情况3必须验证正则条件 $af(n/b) \le cf(n)$($c<1$)
- 主方法不能用的时候(情况2不匹配且不属于情况1/3),改用递归树法
4. 递归树法 ⭐⭐⭐
当主方法不适用时(如 $T(n)=T(n/3)+T(2n/3)+O(n)$),用递归树:
1 | f(n)=n |
5. 算法空间复杂度 ⭐⭐
| 空间类型 | 来源 | 是否可优化 |
|---|---|---|
| 固定空间 | 指令、常量、静态变量 | 一般不关注 |
| 栈空间 | 递归调用栈 | 可转为迭代 |
| 堆空间 | 动态分配(数组、结点) | 算法决定 |
| 总空间 | 固定空间 + 栈空间 + 堆空间 | — |
递归栈空间分析:
- 递归深度为 $d$,每次调用占用 $s$ 空间 → 栈空间 = $O(d \cdot s)$
- 例如快排:平均栈深 $O(\log n)$,最坏 $O(n)$
📙 第3章 伸展树与跳表 ⭐⭐⭐
1. 伸展树(Splay Tree)⭐⭐⭐⭐
为什么需要伸展树?
AVL树虽然保证了 $O(\log n)$ 的最坏查找时间,但每次插入删除都要维护平衡因子、进行旋转,代码复杂。伸展树另辟蹊径:**不刻意维护”完美平衡”,而是利用”访问局部性”**——最近访问过的结点,下次很可能还会访问。
核心思想:每次访问结点(查找/插入/删除)后,通过一系列旋转操作将该结点 “伸展”到树根位置。这样频繁访问的结点就会聚集在靠近根的位置。
伸展操作(Splay):
1 | 三种旋转情况: |
记忆口诀:
- 父是根 → Zig(一下就够)
- 同侧 → Zig-Zig(先父后子)
- 异侧 → Zig-Zag(子转两次,先到父位再到爷位)
均摊分析:伸展树的单次操作可能达到 $O(n)$,但**均摊时间复杂度为 $O(\log n)$**。这是通过势能分析证明的——每次操作虽然可能花 $O(n)$ 时间,但会让树变得更”平衡”,为后续操作节省时间。
对比表:
| 操作 | AVL树 | 伸展树 |
|---|---|---|
| 查找 | $O(\log n)$ 最坏 | $O(\log n)$ 均摊 |
| 插入 | $O(\log n)$ 最坏 | $O(\log n)$ 均摊 |
| 删除 | $O(\log n)$ 最坏 | $O(\log n)$ 均摊 |
| 实现难度 | 复杂 | 中等 |
| 额外空间 | O(n) 平衡因子 | 无需额外空间 |
| 访问局部性利用 | 否 | 是 ✅ |
2. 跳表(Skip List)⭐⭐⭐
基本思想:在有序单链表上添加多层索引指针,上层索引跳过大量中间元素,实现类似二分查找的效果。
结构示意:
1 | Level 3: head ──────────────────→ 30 ──────────────→ null |
查找过程(以查找 35 为例):
1 | ① 从最高层(Level 3)开始:head→30→null,30<35,走到30 |
结点提升规则:每个新插入的结点,以概率 $p$(通常 $p=1/2$)决定是否插入到上一层。相当于”抛硬币”决定每层是否保留该结点。
1 | 第 i 层期望结点数 = n · p^i |
复杂度总结:
| 操作 | 时间复杂度(期望) | 最坏情况 |
|---|---|---|
| 查找 | $O(\log n)$ | $O(n)$(全在Level 0) |
| 插入 | $O(\log n)$ | $O(n)$ |
| 删除 | $O(\log n)$ | $O(n)$ |
| 空间 | $O(n)$ | $O(n)$(期望1/(1-p)·n) |
跳表 vs 平衡树:
| 特性 | 跳表 | AVL/红黑树 |
|---|---|---|
| 实现复杂度 | 简单(约100行) | 复杂(旋转、染色) |
| 查找性能 | $O(\log n)$ 期望 | $O(\log n)$ 最坏保证 |
| 范围查询 | 天然支持 | 需中序遍历 |
| 并发友好度 | 较高 | 较低(锁整树) |
| 空间开销 | 稍大(多指针) | 较小 |
考试提示:跳表考概率分析的可能性不大,但要知道基于”随机化”的数据结构思想——用随机性换取实现简单性。
📕 第4章 基本搜索和遍历方法 ⭐⭐⭐
1. 深度优先搜索(DFS)⭐⭐⭐⭐⭐
核心策略:一条路走到底,走到死胡同再回头。类似于走迷宫——沿着一条路一直走,碰到墙就退回到最近的岔路口换条路。
1 | // 递归版(最常用) |
DFS 执行过程示例:
1 | 图: 0──1──3 |
DFS 时间复杂度:
- 邻接表:$O(V + E)$ —— 每个顶点访问一次 + 每条边检查一次
- 邻接矩阵:$O(V^2)$ —— 每个顶点需要检查 V 个可能的邻接点
2. 广度优先搜索(BFS)⭐⭐⭐⭐⭐
核心策略:先访问近的,再访问远的。从起点出发,逐层向外扩展——先访问距离为1的结点,再访问距离为2的结点……
1 | void BFS(int s) { |
⚠️ 为什么入队时就标记:如果出队时才标记,同一个结点可能被多次入队,导致重复访问甚至死循环。
BFS 执行过程示例:
1 | 图: 0──1──3 |
BFS 求无权图最短路径:BFS 按距离递增的顺序访问结点,因此从起点到一个结点第一次被访问时的路径就是最短路径。
1 | void BFS_ShortestPath(int s) { |
3. DFS 与 BFS 全面对比 ⭐⭐⭐⭐⭐
| 维度 | DFS | BFS |
|---|---|---|
| 数据结构 | 栈(递归/显式栈) | 队列 |
| 搜索策略 | 纵向优先,一条路走到底 | 横向优先,逐层扩展 |
| 空间复杂度 | $O(h)$, $h$ = 树/图的最大深度 | $O(w)$, $w$ = 树/图的最大宽度 |
| 时间复杂度 | $O(V+E)$(邻接表) | $O(V+E)$(邻接表) |
| 适用场景 | 连通性检测、拓扑排序、回溯法、强连通分量 | 无权图最短路径、层序遍历、社交网络N度人脉 |
| 解的性质 | 不一定最短 | 无权图上一定最短 |
| DFS树的边分类 | 树边、回边、前向边、交叉边 | 树边、交叉边(无回边和前向边) |
| 递归实现 | 自然、简洁 | 不自然,用队列迭代 |
| 栈空间风险 | 深度大时可能栈溢出 | 宽度大时内存占用多 |
考试提示:DFS的四类边是理解很多图算法的基础,必须掌握。
4. DFS 边的分类 ⭐⭐⭐
在有向图 DFS 树中:
1 | 树边(Tree Edge): DFS树中的边,v是w的父结点 |
1 | 0 (d=1, f=10) |
5. 括号定理 ⭐⭐⭐
在 DFS 遍历中,每个结点 $v$ 有两个时间戳:
- 发现时间 $d[v]$:$v$ 首次被访问(着色为灰色)
- 完成时间 $f[v]$:$v$ 的所有后继访问完毕(着色为黑色)
括号定理:对于任意两个结点 $u$ 和 $v$,它们的区间 $[d[u], f[u]]$ 和 $[d[v], f[v]]$ 满足以下三者之一:
- 完全分离:$f[u] < d[v]$ 或 $f[v] < d[u]$($u$ 和 $v$ 不在同一DFS树分支中)
- 完全嵌套:$[d[v], f[v]] \subset [d[u], f[u]]$($v$ 是 $u$ 的后代)
- 完全嵌套:$[d[u], f[u]] \subset [d[v], f[v]]$($u$ 是 $v$ 的后代)
不可能出现“部分重叠”的情况!这正是”括号”名称的由来。
推论:$v$ 是 $u$ 的后代当且仅当 $d[u] < d[v] < f[v] < f[u]$
6. 双连通分量与关节点 ⭐⭐⭐
关节点(Articulation Point):删除后导致图不再连通的顶点。
1 | 图: A──B──C |
利用 DFS 求关节点:
定义两个数组:
dfn[v]:DFS 访问编号(深度优先数)low[v]:从 $v$ 出发,通过最多一条回边能到达的最小 dfn
判定条件:
- 根结点:若 DFS 树中根有两个或更多孩子 → 根是关节点
- 非根结点 $v$:若存在孩子 $w$ 使得 $low[w] \ge dfn[v]$ → $v$ 是关节点(意味着 $w$ 及其后代无法绕过 $v$ 到达 $v$ 的祖先)
📓 第5章 分治法 ⭐⭐⭐⭐
1. 分治法基本思想 ⭐⭐⭐⭐⭐
分治法(Divide and Conquer) 是算法设计中最经典的策略之一。核心思路非常简单:大问题不好解决,就把它拆成小问题,各个击破,再合并答案。
1 | 原问题(规模 n) |
分治法适用条件:
- 问题规模缩小到一定程度可以容易地直接求解
- 问题可以分解为若干规模较小的相同问题(最优子结构)
- 子问题的解可以合并为原问题的解
- 子问题之间相互独立(与 DP 的关键区别)
2. 二分搜索 ⭐⭐⭐⭐
问题:在已排序的数组 $a[0..n-1]$ 中查找值为 $x$ 的元素。
1 | int BinarySearch(int a[], int n, int x) { |
1 | 示例:在 [2, 5, 8, 12, 16, 23, 38, 56, 72, 91] 中查找 23 |
递推方程:$T(n) = T(n/2) + O(1)$,解得 $T(n) = O(\log n)$
3. 归并排序 ⭐⭐⭐⭐⭐
思想:将数组二分,分别排序,然后合并两个有序子数组。精髓在**合并(Merge)**操作。
1 | void Merge(int a[], int left, int mid, int right) { |
分治过程示意:
1 | [38,27,43,3,9,82,10] |
- 时间复杂度:$T(n) = 2T(n/2) + O(n)$,主方法情况2 → $O(n\log n)$
- 空间复杂度:$O(n)$(需要辅助数组)
- 稳定排序:相等元素的相对顺序不变
4. 快速排序 ⭐⭐⭐⭐⭐
思想:选一个基准元素(pivot),将数组划分为”小于pivot”和”大于pivot”两部分,再递归排序两部分。
1 | int Partition(int a[], int left, int right) { |
划分过程详解(以 [38, 27, 43, 3, 9, 82, 10] 为例,pivot=38):
1 | 初始: [38, 27, 43, 3, 9, 82, 10] left=0(pivot=38), right=6 |
快排性能分析:
| 情况 | 条件 | 递推式 | 时间复杂度 |
|---|---|---|---|
| 最好/平均 | 每次均匀划分 | $T(n)=2T(n/2)+O(n)$ | $O(n\log n)$ |
| 最坏 | 每次分出一个空集 | $T(n)=T(n-1)+O(n)$ | $O(n^2)$(退化为选择排序) |
**何时退化为 $O(n^2)$**:
- 数组已经有序(或逆序),且选第一个/最后一个元素为 pivot
- 数组所有元素相等(如果用
<=和>=划分)
改进方案:
- 随机选 pivot(期望 $O(n\log n)$)
- 三数取中法(median-of-three)
- 小数组切换为插入排序
考试提示:快排的划分过程(PARTITION)是考试最高频代码之一,必须能手写。退化条件的理解也很重要。
5. 分治法求最大最小元 ⭐⭐⭐
问题:在数组中找到最大值和最小值,尽量减少比较次数。
朴素做法:分别找最大值(n-1次)和最小值(n-1次) → $2n-2$ 次比较
分治做法:
1 | ① 二分数组为两半 |
递推式:$T(n) = 2T(n/2) + 2$,解得 $T(n) = \frac{3n}{2} - 2$,比 $2n-2$ 少约 25%。
6. Strassen 矩阵乘法 ⭐⭐
问题:计算两个 $n \times n$ 矩阵的乘积。
普通算法:$O(n^3)$(三重循环)
Strassen 的巧妙之处:将8次 $n/2 \times n/2$ 乘法减少为 7次!
1 | 递推式:T(n) = 7T(n/2) + O(n²) |
虽然理论上优于 $O(n^3)$,但由于常数因子大,实际应用仍以优化后的传统算法为主。
📒 第6章 贪心法 ⭐⭐⭐⭐⭐
1. 贪心法核心思想 ⭐⭐⭐⭐⭐
贪心法(Greedy Method)是求解最优化问题的一种策略。它的哲学很简单:每一步都做当前看起来最好的选择,相信这些局部最优选择最终会拼出全局最优解。
1 | 贪心法不像 DP 那样"瞻前顾后",它就盯着眼前这一步的最大利益。 |
贪心法算法模板:
1 | SolutionType Greedy(SType a[], int n) { |
贪心法两大关键要素:
| 要素 | 含义 | 为什么重要 |
|---|---|---|
| 最优量度标准 | 每一步选择的依据(如选性价比最高的物品) | 选错了准则 → 得不到最优解 |
| 最优子结构 | 最优解包含子问题的最优解 | 是贪心法能用的前提条件 |
⚠️ 重要提醒:贪心法不总是能得到全局最优解!使用贪心法之前必须证明量度标准的正确性。如果无法证明,就不能保证答案是最优的。
2. 背包问题 ⭐⭐⭐⭐⭐
一般背包问题(物品可分割)
贪心策略:按单位重量收益 $p_i/w_i$ 从大到小排序,优先装性价比最高的物品。
1 | 示例:M=20, (w₀,w₁,w₂)=(18,15,10), (p₀,p₁,p₂)=(25,24,15) |
1 | void GreedyKnapsack(float *x) { |
时间复杂度:$O(n\log n)$(排序主导)
0/1背包问题(物品不可分割)
贪心法不能保证得到0/1背包的最优解!
1 | 反例:M=50, 三件物品 |
为什么贪心会出错? 因为0/1背包的选择有”排他性”——选了高性价比的小物品可能挤占容量,导致无法装入虽性价比略低但总价值更高的大物品。
3. 带时限的作业排序 ⭐⭐⭐⭐
问题:$n$ 个作业,每个运行 1 单位时间,截止期限 $d_i$,收益 $p_i$(按降序排列)。选一个子集使得都能在期限内完成且总收益最大。
核心技巧:每个作业尽可能安排在靠后的时间片(越靠后越难安排,给前面的作业留出空间)。
1 | 例:n=4, d=(2,1,2,1), p=(100,10,15,27)(已按收益降序) |
1 | int JS(int *d, int *x, int n) { |
时间复杂度:$O(n^2)$
并查集优化版(FJS):用并查集快速找到离截止时间最近的空闲时间片,时间复杂度接近 $O(n)$。
4. 最佳合并模式 = 哈夫曼树 ⭐⭐⭐⭐
问题:合并 $n$ 个长度不同的有序文件,每次合并两个文件的成本是两文件长度之和,求使总成本最小的合并顺序。
这其实就是哈夫曼编码问题的变体!
1 | 例:文件长度分别为 5, 10, 20, 30, 30 |
K路合并:推广为 K 路合并(每次合并 K 个文件),需要补充 虚结点(零权值):
- 补充数 = $(K-1) - (n-1)%(K-1)$ 个
- 例如 n=8, K=3:$(3-1) - (8-1)%(3-1) = 2-1 = 1$ 个虚结点
5. 最小代价生成树(MST)⭐⭐⭐⭐⭐
Prim 算法:从一个顶点开始,每次选一条连接树内外的最小边。
1 | 示例图(邻接矩阵): |
Kruskal 算法:按边权从小到大选边,不形成回路即可。
1 | 同样上图,边按权值排序: |
Prim vs Kruskal 对比:
| 维度 | Prim | Kruskal |
|---|---|---|
| 策略 | 结点扩展 | 边扩展 |
| 数据结构 | nearest[], lowcost[] |
并查集 + 优先权队列 |
| 时间复杂度 | $O(V^2)$(邻接矩阵) | $O(E\log E)$ |
| 堆优化 | $O(E\log V)$ | — |
| 适合场景 | 稠密图($E\approx V^2$) | 稀疏图($E\approx V$) |
考试提示:MST是图论的绝对核心考点!手工模拟 Prim 和 Kruskal 都要熟练掌握。MST性质的证明思路要清楚。
6. 迪杰斯特拉(Dijkstra)最短路径 ⭐⭐⭐⭐⭐
Dijkstra 算法求单源最短路径(非负权有向图)。
数据结构:
d[i]:源点到 $i$ 的当前最短距离path[i]:最短路径上 $i$ 的前驱结点(用于回溯路径)inS[i]:是否已确定最短路径
Dijkstra 手工模拟示例:
1 | 图(邻接矩阵,INF表示无边): |
⚠️ Dijkstra 适用条件:所有边权必须非负!有负权边时需用 Bellman-Ford 算法。
7. 贪心法证明方法论 ⭐⭐⭐⭐
| 方法 | 核心思路 | 适用场景 |
|---|---|---|
| 归纳法 | 证明第一步贪心选择不会丢掉最优解,然后对剩余问题归纳 | MST、最佳合并模式 |
| 交换论证 | 假设最优解与贪心解不同,通过交换元素证明贪心解不差 | 背包、作业排序 |
| 反证法 | 假设贪心解不是最优,导出矛盾 | Dijkstra、MST性质 |
交换论证模板(以背包问题为例):
1 | ① 假设贪心解G不是最优,则存在最优解O ≠ G |
📔 第7章 动态规划法 ⭐⭐⭐⭐⭐
1. 动态规划核心思想 ⭐⭐⭐⭐⭐
动态规划(Dynamic Programming)解决的是具有重叠子问题的最优化问题。
DP vs 分治法 vs 贪心法:
| 分治法 | 贪心法 | 动态规划 | |
|---|---|---|---|
| 子问题关系 | 独立 | — | 重叠 ✅ |
| 求解方向 | 自顶向下 | 自顶向下(逐步) | 自底向上 ✅ |
| 存储子解 | 不需要 | 不需要 | 需要(记忆化) ✅ |
| 最优子结构 | 有 | 有 | 有 |
| 保证最优? | — | 不一定(需证明) | 一定 ✅ |
DP 四步法:
1 | ① 刻画最优解的结构特征 ← 什么是一个"状态"? |
2. 最长公共子序列(LCS)⭐⭐⭐⭐⭐
问题:给定两个序列 X 和 Y,求它们的最长公共子序列。
子序列 ≠ 子串:子序列不要求连续,子串要求连续。
例如 X=”abcdefg”,”aceg” 是子序列但不是子串。
DP 方程:
$$dp[i][j] = \begin{cases} 0 & i=0 \text{ 或 } j=0 \ dp[i-1][j-1] + 1 & X[i] = Y[j] \ \max(dp[i-1][j], dp[i][j-1]) & X[i] \neq Y[j] \end{cases}$$
LCS 手工填表演示($X=ABCBDAB$, $Y=BDCABA$):
1 | "" B D C A B A |
回溯构造 LCS(从右下角 dp[7][6]=4 开始):
1 | dp[7][6]=4(不同),← ← dp[7][5]=4(相同!X[7]=B=Y[5])→ 选B |
考试提示:LCS 填表 + 回溯必须熟练掌握,期末考试手算常考!
3. 矩阵连乘问题 ⭐⭐⭐⭐
问题:矩阵乘法满足结合律但不满足交换律。不同的加括号方式导致不同的乘法次数。求最优加括号方案使总乘法次数最少。
问题建模:设 $n$ 个矩阵 $A_1 \times A_2 \times … \times A_n$,$A_i$ 的维数为 $p_{i-1} \times p_i$
DP 方程:
$$m[i][j] = \begin{cases} 0 & i=j \ \min\limits_{i \le k < j}{m[i][k] + m[k+1][j] + p_{i-1}p_k p_j} & i < j \end{cases}$$
手工填表示例($p=[30,35,15,5,10,20]$,即 5 个矩阵):
1 | 对角线上 m[i][i]=0: |
考试提示:要理解为什么按对角线方向填表——因为计算 $m[i][j]$ 需要依赖 $m[i][k]$ 和 $m[k+1][j]$($i \le k < j$),必须保证子问题先求解。
4. 0/1背包问题 ⭐⭐⭐⭐⭐
DP 方程:
$$dp[i][j] = \begin{cases} dp[i-1][j] & w_i > j \text{(装不下,不选)} \ \max(dp[i-1][j],; dp[i-1][j-w_i] + p_i) & w_i \le j \text{(选或不选取大)} \end{cases}$$
手工填表示例($M=10$, $(w,p)=[(2,6),(2,3),(6,5),(5,4),(4,6)]$):
1 | 物品\容量 0 1 2 3 4 5 6 7 8 9 10 |
一维优化(常见考试写法):
1 | for (int i = 1; i <= n; i++) |
为什么会错如果正序? 因为 $dp[j-w[i]]$ 可能已经被当前物品更新过,导致同一物品被多次选取(变成完全背包)。
5. 最优二叉搜索树(OBST)⭐⭐⭐
问题:给定键 $k_1<k_2<…<k_n$ 的查找概率 $p_i$ 和虚键 $d_0<d_1<…<d_n$ 的概率 $q_j$,构造期望搜索代价最小的 BST。
DP 方程:
$$w(i,j) = w(i,j-1) + p_j + q_j$$
$$c(i,j) = \min_{i < k \le j}{c(i,k-1) + c(k,j)} + w(i,j)$$
$w(i,j)$ 是子树包含的所有概率之和(该子树增加一层,期望代价增加 $w(i,j)$)。
6. 多段图问题 ⭐⭐⭐
问题:将图分为 $k$ 个阶段,边只能从前一阶段指向后一阶段,求源点到终点的最短路径。
DP 从后往前推:
1 | 阶段4 阶段3 阶段2 阶段1 阶段0 |
7. DP 问题对比总表 ⭐⭐⭐⭐⭐
| 问题 | 状态定义 | 递推方向 | 时间 | 空间 | 构造最优解 |
|---|---|---|---|---|---|
| 多段图 | dp[i]=i到终点最短距离 | 后→前 | $O(V+E)$ | $O(V)$ | path[i] |
| 矩阵连乘 | $m[i][j]$=Ai..Aj最小乘法数 | 对角线 | $O(n^3)$ | $O(n^2)$ | s[i][j] |
| LCS | $dp[i][j]$=X[1..i]与Y[1..j]的LCS长度 | 行→下 | $O(mn)$ | $O(mn)$ | 箭头回溯 |
| OBST | $c(i,j)$=子树最小代价 | 对角线 | $O(n^3)$ | $O(n^2)$ | r(i,j) |
| 0/1背包 | $dp[i][j]$=前i件容量j的最大价值 | 行→下 | $O(nM)$ | $O(M)$ | 回溯判断 |
考试提示:DP 题三步走——①写方程 → ②填表 → ③回溯,三步都有分,跳步就丢分!
📚 第8章 回溯法 ⭐⭐⭐⭐
1. 回溯法核心思想 ⭐⭐⭐⭐⭐
回溯法 = 深度优先搜索 + 剪枝
1 | 回溯法系统地搜索解空间树。它与暴力枚举(穷举法)的根本区别在于: |
解空间树两种类型:
1 | 子集树(每个元素选或不选): 排列树(n个元素的各种排列): |
2. n皇后问题 ⭐⭐⭐⭐⭐
关键约束:任意两个皇后不能在同一行、同一列、同一对角线。
1 | 棋盘编号(4皇后为例): |
约束检查函数(Place):
1 | bool Place(int k, int i) { |
对角线判断:
abs(x[j]-i) == abs(j-k)是核心公式!两个位置在同一对角线上当且仅当行差等于列差。
4皇后解空间树部分剪枝示意:
1 | root |
3. 子集和数问题 ⭐⭐⭐⭐
问题:给定集合 W 和目标 M,求所有和为 M 的子集。
两个关键剪枝条件:
| 条件 | 公式 | 含义 |
|---|---|---|
| 不超目标 | $s + w_k \le M$ | 选了当前元素不能超目标 |
| 还能达到 | $s + r_k \ge M$ | 不选的话,剩下的还够达到目标 |
其中 $s$ = 当前部分和,$r_k$ = 剩余元素之和。
1 | 例:W={5, 10, 12, 13, 15, 18}, M=30 |
4. 回溯法 vs 分枝限界法 ⭐⭐⭐⭐
| 维度 | 回溯法 | 分枝限界法 |
|---|---|---|
| 搜索策略 | DFS(深度优先) | BFS / 最佳优先(LC) |
| 目标 | 找出所有解或任意解 | 找出最优解 |
| 活结点结构 | 隐式栈(递归) | 队列(FIFO)或优先权队列(LC) |
| 扩展方式 | 每次深入一层 | 每次扩展一个活结点 |
| 剪枝依据 | 约束函数 + 限界函数 | 上下界函数 |
| 空间开销 | 较小(O(深度)) | 较大(O(宽度)) |
| 典型应用 | n皇后、图着色、哈密顿环 | 0/1背包最优解、TSP |
📜 第9章 分枝限界法 ⭐⭐⭐⭐
1. 分枝限界法核心思想 ⭐⭐⭐⭐⭐
分枝限界法(Branch and Bound)是求解最优化问题的通用框架。它在状态空间树上进行 BFS/最佳优先搜索,利用上下界函数剪枝。
1 | 分枝限界法 = 智能搜索 + 上下界剪枝 |
三种变体:
| 类型 | 活结点表 | 扩展策略 | 终止条件 | 效率 |
|---|---|---|---|---|
| FIFO | 队列 | 先进先出 | 答案结点出队 | 低 |
| LIFO | 栈 | 后进先出 | 答案结点出栈 | 中(类似回溯) |
| LC(最小代价) | 优先权队列 | ĉ 最小的先扩展 | ĉ(E) ≥ U | 最高 ✅ |
LC 是实际使用最多的变体,考试也主要考 LC。
2. 代价估计函数 ⭐⭐⭐⭐⭐
ĉ(X):从根经结点 X 到答案结点的估计最小代价。
$$\hat{c}(X) = f(X) + \hat{g}(X)$$
| 组成部分 | 含义 | 例子 |
|---|---|---|
| $f(X)$ | 从根到 X 的已知代价 | 已选物品总重量、已走路径长度 |
| $\hat{g}(X)$ | 从 X 到答案结点的估计代价(下界估计) | 剩余容量按贪心可装入的价值 |
关键性质:必须满足 $\hat{c}(X) \le c(X)$($c(X)$ 是真实最小代价),即 **”估计值 ≤ 真实值”**,否则可能把最优解也剪掉了!
3. 上下界函数详解 ⭐⭐⭐⭐⭐
1 | ┌─── 上界 u(X):乐观估计"从X往下最好能多好" |
| 符号 | 含义 | 怎么用 |
|---|---|---|
| $c(X)$ | 真实最优代价 | 难以直接计算,是目标 |
| $\hat{c}(X)$ | $c(X)$ 的下界估计 | ① 决定扩展顺序(小的先扩展)② 剪枝(ĉ≥U则剪) |
| $u(X)$ | $c(X)$ 的上界估计 | 更新全局变量 $U$ |
| $U$ | 当前最优解的值 | 剪枝基准:$U = \min{已知可行解的代价}$ |
剪枝判定:若 $\hat{c}(X) \ge U$,说明即使走 X 这条路最优情况也不会比当前已知解更好,直接剪掉。
LCBB 算法流程图:
1 | ┌──────────┐ |
4. 0/1背包问题的分枝限界 ⭐⭐⭐⭐⭐
上界函数(UBB):对剩余物品按贪心法(可分割)计算最大可能收益。
1 | UBB(X) = 已选物品收益 + 剩余容量按贪心可装入的收益(可能装部分物品) |
下界函数(LBB):已选物品的实际收益(不考虑剩余容量)。也可用贪心构造一个可行解的值。
剪枝:若 $UBB(X) \le$ 当前已知最优收益,则剪掉 X。
手工模拟关键步骤:
1 | 假设 M=10,(w,p)=[(4,40),(7,42),(5,25),(3,12)] |
5. TSP(旅行商问题)⭐⭐⭐
归约矩阵(Reduction):每行减去该行最小值,每列减去该列最小值。
1 | 原始矩阵: 行归约后: 列归约后: |
选择边后,需要删除对应行和列,并设置禁止回路边(如 $(j,i)$ 在 $(i,j)$ 被选后设为 ∞),再对矩阵重新归约。
🎯 期末考法综合总结 ⭐⭐⭐⭐⭐
五大算法策略全景对比
1 | 问题类型 推荐策略 典型问题 |
| 策略 | 核心哲学 | 时间复杂度 | 空间复杂度 | 是否保证最优 |
|---|---|---|---|---|
| 分治法 | 分而治之,独立求解 | 视具体算法 | 递归栈 | 是 |
| 贪心法 | 局部最优 = 全局最优? | 通常 $O(n\log n)$ | $O(1)$ 或 $O(n)$ | 需证明 |
| 动态规划 | 记忆化 + 自底向上 | $O(n^2)\sim O(n^3)$ | $O(n)\sim O(n^2)$ | 是 |
| 回溯法 | DFS + 剪枝 | 指数(剪枝优化) | $O(深度)$ | 是(找所有解) |
| 分枝限界 | BFS/LC + 上下界剪枝 | 指数(剪枝优化) | $O(宽度)$ | 是(找最优解) |
必考知识清单
⭐⭐⭐⭐⭐ 绝对重点(必考)
- 大O/Ω/Θ 定义与证明方法
- 主方法求递推式(三种情况判断)
- 贪心法:背包问题(一般 vs 0/1)、Prim / Kruskal、Dijkstra
- DP:LCS、0/1背包递推方程 + 填表 + 回溯
- 回溯法:n皇后(Place函数)、解空间树类型
⭐⭐⭐⭐ 高频考点
- 算法5个特征(有限性 vs 确定性)
- 时间复杂度大小排序
- 快排/归并排序(代码 + 递推分析)
- 贪心正确性证明方法(交换论证)
- 回溯 vs 分枝限界的区别
- LC分枝限界的 ĉ/U 上下界
⭐⭐⭐ 一般考点
- 伸展树三种旋转(zig/zig-zig/zig-zag)
- 跳表结构与概率分析
- DFS四类边 / BFS最短路径
- 双连通分量 / 关节点判定
- Strassen矩阵乘法 / 最佳合并模式
- TSP归约矩阵下界计算
考试答题模板
大O证明题:
1 | ① 根据定义写出要证的不等式 |
主方法题:
1 | ① 写出 a, b, f(n) |
算法模拟题:
1 | ① 写出初始状态 |
DP 设计题:
1 | ① 定义状态含义(如 dp[i][j] 表示什么) |
最后叮嘱
考试提示:
- 大O证明:找 c 和 n₀,过程写完整,踩分点都在推导步骤
- 主方法:先算 $\log_b a$,再和 $f(n)$ 比;情况3必须验证正则条件
- 算法模拟题:一步步写出过程,禁止跳步,中间状态要清晰
- DP题:递推方程 + 填表 + 回溯构造,三件套缺一不可
- 代码填空:重点掌握 Partition、Place、Dijkstra初始化、Merge 四个核心函数
- 判断题:贪心不一定最优(需证明),DP一定最优,0/1背包贪心不对




