算法设计基础考点总结
算法设计基础考点总结
资料来源:已上传的第 2–8 章 PPT 与原 Markdown 复习稿
适用范围:《算法设计与分析》期末复习
使用方式:先看“4–5星重中之重详解版”,再背“核心考点”,最后做章节练习和综合题
说明:⭐ 表示考试重要程度,⭐⭐⭐⭐⭐ 为必考级别
0. PPT 覆盖总清单 ⭐⭐⭐⭐⭐
| 章节 | PPT 核心范围 | 复习优先级 |
|---|---|---|
| 第2章 算法分析基础 | 算法复杂度、渐近记号、递推关系、主方法 | ⭐⭐⭐⭐⭐ |
| 第3章 伸展树与跳表 | 字典、二叉搜索树、伸展树旋转、跳表结构与查找 | ⭐⭐⭐ |
| 第4章 基本搜索和遍历 | BFS、DFS、时间戳、边分类、括号定理、双连通分量、关节点 | ⭐⭐⭐⭐ |
| 第5章 分治法 | 分治思想、最大最小元、二分搜索、归并排序、快速排序、选择问题、Strassen | ⭐⭐⭐⭐⭐ |
| 第6章 贪心法 | 一般背包、作业排序、最佳合并模式、MST、Dijkstra、磁带最优存储、贪心证明 | ⭐⭐⭐⭐⭐ |
| 第7章 动态规划法 | DP基本要素、多段图、矩阵连乘、LCS、OBST、0/1背包、备忘录 | ⭐⭐⭐⭐⭐ |
| 第8章 回溯法 | 回溯一般方法、n皇后、子集和、图着色、哈密顿环、0/1背包、批处理作业调度 | ⭐⭐⭐⭐ |
考试策略:
- 概念题优先背“定义 + 适用条件 + 复杂度”。
- 算法设计题必须写出:状态/选择、递推或剪枝、算法步骤、复杂度。
- 证明题重点是:渐近记号证明、贪心正确性证明、DP最优子结构证明。
- 模拟题重点是:Dijkstra、Prim/Kruskal、LCS表、矩阵连乘表、回溯搜索树。
4–5星重中之重详解版:先看这一部分 ⭐⭐⭐⭐⭐
本节专门补强所有 ⭐⭐⭐⭐ 与 ⭐⭐⭐⭐⭐ 内容。
目标不是“背概念”,而是让你知道:考试为什么考、题目怎么出、答案怎么写、怎么算、怎么证明。
建议复习顺序:第2章复杂度 → 第5章分治 → 第6章贪心 → 第7章DP → 第8章回溯 → 第4章DFS/BFS → 第3章伸展树。
一、第2章:复杂度、渐近记号、递推关系 ⭐⭐⭐⭐⭐
1. 时间复杂度到底在分析什么?⭐⭐⭐⭐⭐
时间复杂度不是在问“程序运行几秒”,而是在问:
当输入规模 $n$ 变大时,算法运行时间按什么速度增长。
例如:
1 | for (int i = 0; i < n; i++) { |
循环执行 $n$ 次,所以时间复杂度是:
再看双重循环:
1 | for (int i = 0; i < n; i++) { |
外层执行 $n$ 次,每次内层执行 $n$ 次,总共 $n^2$ 次,所以:
如果内层不是从 $0$ 到 $n$,而是从 $i$ 到 $n$:
1 | for (int i = 0; i < n; i++) { |
执行次数为:
去掉常数和低阶项后仍是:
考试易错点:
不要看到两层循环就机械写 $O(n^2)$,要看每层循环次数是否和 $n$ 有关。例如:
1 | for (int i = 0; i < n; i++) { |
总次数为 $100n$,所以复杂度是:
不是 $O(n^2)$。
2. 最好、最坏、平均复杂度怎么区分?⭐⭐⭐⭐
以顺序查找为例,在数组中查找关键字 $x$:
1 | for (int i = 0; i < n; i++) { |
| 情况 | 发生场景 | 比较次数 | 复杂度 |
|---|---|---|---|
| 最好情况 | 第一个元素就是 $x$ | 1 | $O(1)$ |
| 最坏情况 | 最后一个元素才是 $x$,或不存在 | $n$ | $O(n)$ |
| 平均情况 | $x$ 等概率出现在任意位置 | $(n+1)/2$ | $O(n)$ |
考试默认通常问的是最坏情况复杂度,因为最坏情况能给算法性能提供保证。
3. 渐近记号:大O、大Ω、大Θ怎么写证明?⭐⭐⭐⭐⭐
(1)大O:证明“不会超过”
要证明:
就是要找两个常数 $c>0,n_0>0$,使得:
例:证明
当 $n\ge1$ 时:
所以:
取:
故:
答题模板:
1 | 当 n ≥ n0 时,有低阶项 ≤ 若干倍最高阶项, |
(2)大Ω:证明“至少这么快”
要证明:
就是找 $c,n_0$,使得:
例:
因为:
取 $c=3,n_0=1$ 即可。
(3)大Θ:证明“同阶”
要证明:
需要同时证明:
和:
例:
上面已证:
又因为:
因此:
所以:
考试最常见错误:
写“大O就是最坏情况,大Ω就是最好情况”。这不严谨。大O/Ω/Θ本质是函数增长上界/下界/紧界,不直接等于最好/最坏情况。
4. 递推关系怎么解?⭐⭐⭐⭐⭐
递归算法常常得到递推式。考试经常要求你从递推式推出复杂度。
方法1:展开法
例:
展开:
所以:
方法2:递归树法
例:
画递归树:
| 层数 | 子问题数 | 每个子问题规模 | 每层总代价 |
|---|---|---|---|
| 0 | 1 | $n$ | $n$ |
| 1 | 2 | $n/2$ | $n$ |
| 2 | 4 | $n/4$ | $n$ |
| … | … | … | … |
| $\log n$ | $n$ | 1 | $n$ |
共有 $\log n+1$ 层,每层代价 $n$,所以:
方法3:主方法
遇到:
先算:
然后比较 $f(n)$ 与 $n^{\log_b a}$。
例1:
有:
而:
比 $n^2$ 小,所以:
例2:
有:
$f(n)=n$ 与它同阶,所以:
例3:
有:
$f(n)=n^2$ 更大,所以:
主方法答题模板:
1 | 该递推式中 a=..., b=..., f(n)=... |
二、第3章:伸展树重难点详解 ⭐⭐⭐⭐
1. 为什么伸展树不用严格平衡也能快?⭐⭐⭐⭐
普通二叉搜索树的问题是:
如果插入序列是有序的,例如:
树可能退化成链表,查找最坏需要 $O(n)$。
AVL树、红黑树的做法是:
每次插入删除后维护严格或近似平衡。
伸展树的做法不同:
它不维护显式平衡,而是把“刚访问过的结点”旋转到根。
这样做基于一个经验规律:
刚刚访问过的元素,很可能近期还会再次访问。把它放到根附近,可以降低后续访问代价。
所以伸展树的单次操作最坏可能是 $O(n)$,但连续多次操作的平均分摊代价是:
考试答题时要强调:
1 | 伸展树不是严格平衡树,而是自调节搜索树。 |
2. 伸展操作到底怎么判断?⭐⭐⭐⭐
设当前伸展结点为 $x$,父结点为 $p$,祖父结点为 $g$。
情况1:父结点就是根
只做一次单旋。
| $x$ 位置 | 操作 |
|---|---|
| $x$ 是 $p$ 左孩子 | zig,右旋 |
| $x$ 是 $p$ 右孩子 | zag,左旋 |
情况2:$x,p,g$ 同向
若 $x$ 是 $p$ 的左孩子,$p$ 是 $g$ 的左孩子:
1 | g |
这是 zig-zig。操作顺序:
- 先对 $g$ 右旋;
- 再对 $p$ 右旋。
若 $x$ 是 $p$ 的右孩子,$p$ 是 $g$ 的右孩子,就是 zag-zag,对称地做两次左旋。
情况3:$x,p,g$ 反向
若 $x$ 是 $p$ 的右孩子,$p$ 是 $g$ 的左孩子:
1 | g |
这是 zig-zag。操作顺序:
- 先对 $p$ 左旋;
- 再对 $g$ 右旋。
若 $x$ 是 $p$ 的左孩子,$p$ 是 $g$ 的右孩子,就是 zag-zig,对称处理。
记忆方法:
1 | 同向:先转祖父,再转父亲。 |
三、第4章:BFS、DFS、关节点 ⭐⭐⭐⭐⭐
1. BFS为什么能求无权图最短路径?⭐⭐⭐⭐⭐
BFS是“按层扩展”的:
- 第0层:源点 $s$;
- 第1层:距离 $s$ 为1条边的结点;
- 第2层:距离 $s$ 为2条边的结点;
- 第3层:距离 $s$ 为3条边的结点。
由于队列先进先出,BFS一定先处理距离小的结点,再处理距离大的结点。
因此,当某个结点第一次被BFS发现时,得到的路径一定是从源点到该结点的最短边数路径。
标准证明思路:
1 | BFS从源点开始逐层访问。 |
注意:
BFS只能直接求无权图最短路径。
如果图有权值,不能简单用BFS,应该用Dijkstra、Bellman-Ford或Floyd等算法。
2. DFS时间戳怎么理解?⭐⭐⭐⭐⭐
DFS给每个结点两个时间:
- $d[u]$:第一次发现 $u$ 的时间;
- $f[u]$:从 $u$ 出发的所有边都处理完的时间。
因为DFS会“一条路走到底”,所以一个结点的所有后代都会在它完成之前完成。
若 $v$ 是 $u$ 的后代,则一定有:
这就是括号定理。
可以理解成:
1 | u 被打开左括号:d[u] |
所以区间 $[d[v],f[v]]$ 完全包含在 $[d[u],f[u]]$ 中。
考试会怎么出:
给你几个点的 $d,f$ 值,判断祖先/后裔关系。
例:
因为:
所以 $v$ 是 $u$ 的后裔。
3. DFS边分类怎么判断?⭐⭐⭐⭐
在有向图DFS中,边可以分成四类:
| 边 | 判断方法 | 含义 |
|---|---|---|
| 树边 | 访问白色结点 | DFS真正走过的边 |
| 反向边 | 指向灰色祖先 | 表示存在环 |
| 正向边 | 指向黑色后裔 | 祖先到后裔的非树边 |
| 交叉边 | 指向其他黑色结点 | 不属于祖先后裔关系 |
最重要结论:
为什么?
反向边是从某个结点指回它的祖先,祖先到该结点已有一条DFS树路径,再加上这条反向边就构成环。
答题模板:
1 | 若DFS中存在反向边 (u,v),则v是u的祖先。 |
4. 关节点和Low值为什么这么判?⭐⭐⭐⭐
(1)根结点
DFS树的根如果有两个或更多孩子,则它是关节点。
原因:
根的不同孩子子树之间不能通过非树边互相连接,否则它们会在DFS中属于同一个子树。删除根后,这些子树分离。
所以:
1 | 根结点是关节点 ⇔ 根在DFS树中至少有两个孩子。 |
(2)非根结点
对非根结点 $u$,若存在孩子 $w$ 满足:
则 $u$ 是关节点。
这里 $Low[w]$ 表示:
从 $w$ 的子树出发,最多经过一条反向边,能到达的最早祖先的发现时间。
若:
说明 $w$ 的子树能绕过 $u$ 回到 $u$ 的祖先,删除 $u$ 后仍可连通到上面。
若:
说明 $w$ 子树无法绕过 $u$ 到达 $u$ 的祖先。删除 $u$ 后,$w$ 子树断开,所以 $u$ 是关节点。
答题模板:
1 | Low[w] 表示 w 子树能通过反向边到达的最早祖先。 |
四、第5章:分治法核心算法详解 ⭐⭐⭐⭐⭐
1. 分治法为什么通常得到递归?⭐⭐⭐⭐⭐
分治法三步:
- 分解 Divide;
- 求解 Conquer;
- 合并 Combine。
因为子问题与原问题类型相同,只是规模变小,所以最自然的写法就是递归。
例如归并排序:
1 | 要排序 A[0..n-1] |
这显然又调用“排序”自身,所以是递归。
2. 分治和动态规划怎么区分?⭐⭐⭐⭐⭐
这类题非常容易考简答。
| 对比点 | 分治 | 动态规划 |
|---|---|---|
| 子问题 | 相互独立 | 大量重叠 |
| 求解方式 | 递归求解后合并 | 保存子问题结果 |
| 是否重复计算 | 一般无严重重复 | 若不用表会大量重复 |
| 典型例子 | 归并排序、快速排序 | LCS、矩阵连乘、0/1背包 |
例:归并排序中,左半部分和右半部分互不重叠,所以适合分治。
例:斐波那契递归中,$F(n-1)$ 和 $F(n-2)$ 都会反复用到 $F(n-3),F(n-4)$ 等子问题,所以适合DP。
标准答案:
1 | 分治法要求子问题相互独立,递归求解后合并; |
3. 二分搜索为什么是 $O(\log n)$?⭐⭐⭐⭐⭐
二分搜索每次比较后,问题规模减半:
设经过 $k$ 次后规模变为1:
所以:
因此二分搜索复杂度为:
考试细节:
二分搜索必须满足前提:表有序。
若数据无序,不能直接二分。
4. 归并排序为什么稳定且一定 $O(n\log n)$?⭐⭐⭐⭐⭐
归并排序每次一分为二,递归深度为:
每一层都要把所有元素合并一次,总合并代价是:
所以:
它与数据初始顺序无关,无论最好、平均、最坏都是:
稳定性来自合并时的规则:
1 | 当左右两个元素相等时,先取左边元素。 |
这样原来在前面的相等元素仍在前面。
5. 快速排序为什么平均快但最坏慢?⭐⭐⭐⭐⭐
快速排序的关键是分划 pivot。
最好情况
每次pivot都把序列均分:
所以:
最坏情况
每次pivot都是最小或最大,分成:
于是:
展开:
为什么平均情况仍好?
随机情况下,pivot不太可能每次都极端不平衡。平均递归树高度约为 $O(\log n)$,每层分划代价为 $O(n)$,所以平均:
常考对比:
| 排序 | 最坏 | 平均 | 稳定性 | 额外空间 |
|---|---|---|---|---|
| 归并排序 | $O(n\log n)$ | $O(n\log n)$ | 稳定 | $O(n)$ |
| 快速排序 | $O(n^2)$ | $O(n\log n)$ | 不稳定 | 平均 $O(\log n)$ 递归栈 |
6. 选择问题为什么可以做到线性时间?⭐⭐⭐⭐
选择问题是找第 $k$ 小元素,不需要把所有元素完全排序。
如果先排序,再取第 $k$ 个:
但选择问题只需要定位一个元素,可以更快。
基于Partition的选择:
1 | 分划后,pivot位置已经确定。 |
平均情况下每次只处理一边,所以平均:
但如果每次pivot极差,最坏:
线性选择算法通过“中位数的中位数”选择较好pivot,保证每次能丢掉固定比例的元素,所以最坏也能达到:
五、第6章:贪心法核心算法详解 ⭐⭐⭐⭐⭐
1. 贪心法最关键的不是算法,而是证明 ⭐⭐⭐⭐⭐
很多同学觉得贪心法简单,因为“每次选最大的/最小的”。
但考试重点恰恰是:
你凭什么这样选一定最优?
所以贪心题必须写证明。
贪心正确性一般证明两个性质:
(1)贪心选择性质
存在一个最优解,它包含当前的贪心选择。
这通常用交换论证证明:
1 | 设O是一个最优解。 |
(2)最优子结构
做出贪心选择后,剩下的问题仍是同类型的子问题,其最优解与当前选择组合后构成原问题最优解。
考试答题必须写:
1 | 该问题具有贪心选择性质和最优子结构,因此贪心算法正确。 |
不能只写“每次选最优,所以全局最优”。
2. 一般背包为什么能贪心?⭐⭐⭐⭐⭐
一般背包允许物品分割:
所以只要某物品单位价值高,就应该尽可能多装。
贪心准则:
为什么正确?
假设有两个物品 $i,j$,并且:
如果一个方案中还有 $j$ 被装入,而 $i$ 没有尽量装满,那么可以从 $j$ 中拿出一小部分重量 $\Delta$,换成同样重量的 $i$。
收益变化:
收益变大,原方案不可能最优。
所以最优方案一定优先装单位价值高的物品。
例题详解
背包容量 $M=20$,物品:
| 物品 | 重量 | 收益 | 单位收益 |
|---|---|---|---|
| 0 | 18 | 25 | 1.39 |
| 1 | 15 | 24 | 1.60 |
| 2 | 10 | 15 | 1.50 |
排序:物品1 → 物品2 → 物品0。
装入:
- 装物品1,重量15,收益24,剩余容量5;
- 物品2只能装 $5/10=0.5$,收益 $15\times0.5=7.5$。
总收益:
解向量按原编号:
3. 0/1背包为什么不能用同样贪心?⭐⭐⭐⭐⭐
0/1背包中:
物品不能切开。
反例:
背包容量 $M=50$:
| 物品 | 重量 | 价值 | 单位价值 |
|---|---|---|---|
| 1 | 10 | 60 | 6 |
| 2 | 20 | 100 | 5 |
| 3 | 30 | 120 | 4 |
按单位价值贪心:
- 选1,剩余40;
- 选2,剩余20;
- 不能选3;
- 总价值160。
但最优解是选2和3:
所以0/1背包不能保证用单位价值贪心得到最优解。
考试回答:
1 | 一般背包允许分割,单位价值高的物品可以部分装入,所以贪心正确。 |
4. 带时限作业排序怎么做?⭐⭐⭐⭐
题型常给作业收益 $p_i$ 和期限 $d_i$,每个作业耗时1。
目标:在截止期限内完成尽可能高收益的作业集合。
贪心准则:
1 | 按收益从大到小考虑作业。 |
为什么安排在最晚?
因为越早的时间片越宝贵,可能留给截止期限更早的作业。把当前作业放到尽可能晚的位置,可以保留更多灵活性。
例:
| 作业 | 收益 | 截止期 |
|---|---|---|
| A | 100 | 2 |
| B | 19 | 1 |
| C | 27 | 2 |
| D | 25 | 1 |
| E | 15 | 3 |
按收益排序:A、C、D、B、E。
最大期限为3,有时间片1、2、3。
- A截止2,放最晚空位2;
- C截止2,时间片2已占,放时间片1;
- D截止1,时间片1已占,不能放;
- B截止1,不能放;
- E截止3,放时间片3。
选择:C、A、E,总收益:
5. 最佳合并模式为什么每次合并最小两个?⭐⭐⭐⭐
合并文件时,某个文件如果早早被合并,它的长度会在后续合并中反复参与代价。
所以长度小的文件应该放在更深的位置,长度大的文件放在更浅的位置。
这与哈夫曼树完全一致:
- 文件长度是权值;
- 合并过程是构造哈夫曼树;
- 总合并代价是带权外路径长度。
每次选两个最小的合并,能保证总带权路径长度最小。
答题关键词:
1 | 最佳合并模式等价于哈夫曼树构造。 |
6. Prim和Kruskal怎么区分?⭐⭐⭐⭐⭐
两者都求最小生成树,但思路不同。
Prim:扩展一棵树
从一个起点开始,每次选一条连接:
的最小边。
关键词:
1 | 扩点、维护一个已选顶点集合、适合稠密图。 |
Kruskal:从小边开始选
先把所有边按权值排序,每次选最小的边,只要不成环就加入。
关键词:
1 | 选边、排序、并查集判环、适合稀疏图。 |
对比表
| 项目 | Prim | Kruskal |
|---|---|---|
| 每步选择 | 连通树内外的最小边 | 全图当前最小且不成环的边 |
| 维护对象 | 顶点集合 | 边集合 |
| 常用结构 | lowcost数组 | 并查集 |
| 复杂度 | 邻接矩阵 $O(n^2)$ | $O(e\log e)$ |
| 适合 | 稠密图 | 稀疏图 |
7. Dijkstra为什么是贪心算法?⭐⭐⭐⭐⭐
Dijkstra每一步选择:
当前还未确定最短路的结点中,$d$ 值最小的结点。
这就是贪心选择。
为什么这个选择正确?
因为所有边权非负。设当前选中结点 $u$,它的 $d[u]$ 最小。
若存在一条经过其他未选结点再到 $u$ 的更短路径,那么这条路径必须先到某个未选结点 $v$。由于边权非负,从源点到 $v$ 的距离不会小于当前 $d[v]$,而 $d[v]\ge d[u]$,所以不可能再得到比 $d[u]$ 更短的路径。
所以 $u$ 的最短距离可以被最终确定。
Dijkstra手算步骤
- 初始化:
- 源点 $d=0$;
- 其他点 $d=\infty$;
- $S=\emptyset$。
- 选 $V-S$ 中 $d$ 最小的点 $u$ 加入 $S$。
- 用 $u$ 松弛所有邻边:
- 重复直到所有点确定。
考试易错点:
- 不要忘记更新前驱
path[v]; - Dijkstra不能用于有负权边的图;
- 邻接矩阵版本复杂度通常写 $O(n^2)$。
六、第7章:动态规划核心算法详解 ⭐⭐⭐⭐⭐
1. DP题目应该怎么想?⭐⭐⭐⭐⭐
DP最重要的是把问题变成表格。
答题时必须写清楚:
1 | ① 状态定义:dp[i][j]表示什么; |
很多同学丢分是因为只写公式,不解释状态含义。
状态含义没写清楚,公式即使对了也可能被扣分。
2. 最优子结构怎么证明?⭐⭐⭐⭐⭐
以矩阵连乘为例。
如果 $A_i\cdots A_j$ 的最优加括号方式最后在 $k$ 处分开:
那么左半部分 $A_i\cdots A_k$ 必须也是最优加括号。
反证:
如果左半部分不是最优,则存在另一种左半部分加括号方式乘法次数更少。用它替换原方案左半部分,就得到一个更优的整体方案,与“原整体最优”矛盾。
这就是DP最常用证明套路:
1 | 假设整体最优解中包含的某个子问题解不是最优的。 |
3. 矩阵连乘为什么要枚举最后断点?⭐⭐⭐⭐⭐
矩阵乘法满足结合律:
可以有多种加括号方式:
DP的关键是看“最后一次乘法”。
最后一次乘法一定把矩阵链分成左右两段:
其中 $k$ 可以从 $i$ 到 $j-1$。
所以递推为:
其中:
- $m[i][k]$:左半段最优代价;
- $m[k+1][j]$:右半段最优代价;
- $pip{k+1}p_{j+1}$:最后两矩阵相乘代价。
填表顺序:
先算长度为1的链:
再算长度为2、3、4……直到整个链。
因为 $m[i][j]$ 依赖更短区间,所以必须按链长递增填表。
4. LCS为什么有三种情况?⭐⭐⭐⭐⭐
设:
状态:
情况1:最后字符相等
若:
则这个字符可以放入LCS末尾:
情况2:最后字符不相等
若:
那么LCS不可能同时以这两个字符结尾。
只能考虑:
- 不用 $x_i$:$c[i-1][j]$;
- 不用 $y_j$:$c[i][j-1]$。
所以:
恢复LCS的方法:
从 $c[m][n]$ 往回走:
- 若 $x_i=y_j$:输出该字符,走左上;
- 否则走值较大的方向;
- 若上和左相等,任选一个,可能得到不同LCS。
5. 0/1背包DP为什么是“选或不选”?⭐⭐⭐⭐⭐
对第 $i$ 件物品,只有两种选择:
- 不选它;
- 选它。
状态:
不选第 $i$ 件
收益就是:
选第 $i$ 件
前提是:
选了它之后,剩余容量为:
收益为:
所以:
如果 $j<w_i$,装不下,只能不选:
和一般背包区别:
| 问题 | $x_i$ | 常用方法 |
|---|---|---|
| 一般背包 | $0\le x_i\le1$ | 贪心 |
| 0/1背包 | $x_i\in{0,1}$ | 动态规划/回溯/分枝限界 |
七、第8章:回溯法核心算法详解 ⭐⭐⭐⭐⭐
1. 回溯法本质是什么?⭐⭐⭐⭐⭐
回溯法就是:
深度优先搜索状态空间树,发现不可能成功就退回上一层。
它不是简单暴力,因为它有剪枝。
回溯法过程:
1 | 先做一个选择; |
这就是“试探—失败—退回—再试探”。
2. 显式约束和隐式约束怎么区分?⭐⭐⭐⭐⭐
以n皇后为例。
解向量:
其中 $x_i$ 表示第 $i$ 行皇后放在哪一列。
显式约束:
即每个皇后只能放在棋盘列范围内。
隐式约束:
- 任意两个皇后不同列;
- 任意两个皇后不同斜线。
也就是:
且:
区别一句话:
1 | 显式约束定义候选解空间; |
3. n皇后Place函数为什么这样写?⭐⭐⭐⭐⭐
假设已经放好第 $0$ 到 $k-1$ 行,现在尝试第 $k$ 行放在 $x[k]$ 列。
只需要检查之前的皇后:
1 | for (int i = 0; i < k; i++) { |
为什么?
- 同一行不用检查,因为每一层只放一行;
- 同列冲突:$x[i]=x[k]$;
- 同斜线冲突:行差等于列差:
考试写n皇后回溯时,必须写出这个判定条件。
4. 子集和数怎么剪枝?⭐⭐⭐⭐
问题:给定正数集合,找和为 $M$ 的子集。
假设:
- 当前和为 $s$;
- 剩余元素总和为 $r$;
- 下一个元素为 $w_k$。
可以剪枝的情况:
情况1:当前和已经超过目标
由于所有数都是正数,继续加只会更大,所以剪枝。
情况2:即使全选也达不到目标
说明后面所有元素都选上也不够,剪枝。
情况3:选择下一个元素会超过目标
则“选 $w_k$”这条分支不可走。
这类题答题要写清楚:
1 | 因为所有元素为正数,所以当前和超过M后不可能再减少; |
5. 图m着色为什么是 $m^n$ 规模?⭐⭐⭐⭐
有 $n$ 个结点,每个结点有 $m$ 种颜色。
如果不剪枝,候选着色方案数是:
隐式约束是:
任意相邻结点颜色不同。
在第 $k$ 个结点选颜色时,只需检查它与已经染色的邻接点是否冲突:
1 | bool Ok(int k) { |
若冲突,剪去该颜色分支。
6. 0/1背包回溯为什么要用限界函数?⭐⭐⭐⭐
0/1背包是最优化问题。
如果只是用约束函数,只能剪去超重分支,仍可能搜索大量不可能超过当前最优解的分支。
限界函数用于估计:
从当前结点继续往下,理论上最多还能获得多少收益。
若这个上界都不超过当前最优值,就没必要搜索。
常用上界:
用“一般背包贪心”估计剩余物品最大可能收益。因为允许分割会比0/1情况更乐观,所以这是一个合法上界。
若:
则剪枝。
答题模板:
1 | 先按单位价值从大到小排序。 |
4–5星题型标准答题模板汇总 ⭐⭐⭐⭐⭐
1. 复杂度分析题模板
1 | 设输入规模为 n。 |
2. 分治算法设计题模板
1 | Divide:将规模为 n 的问题分成若干个同类型子问题; |
3. 贪心算法设计题模板
1 | 贪心准则:每一步选择 ... |
4. 动态规划算法设计题模板
1 | 状态定义:dp[i][j] 表示 ... |
5. 回溯算法设计题模板
1 | 解向量:X=(x1,x2,...,xn),其中 xi 表示 ... |
第2章 算法分析基础 ⭐⭐⭐⭐⭐
2.1 好算法与复杂度基础 ⭐⭐⭐⭐
一个好的算法通常具有四个重要特性:
| 特性 | 含义 | 考试提醒 |
|---|---|---|
| 正确性 | 算法结果满足题目要求 | 证明算法必须先说明正确性 |
| 简明性 | 思路清晰,易理解和实现 | 伪代码要层次清楚 |
| 效率 | 时间与空间使用合理 | 复杂度分析是必考点 |
| 最优性 | 达到该类问题所需时间下界 | 常与“排序下界”“搜索下界”结合考 |
程序健壮性:输入不合法时程序能做适当处理,不造成严重后果。
PPT说明:本课程默认算法输入合法,通常不重点讨论输入检测。
影响程序运行时间的因素:
- 程序所依赖的算法;
- 问题规模和输入数据;
- 计算机系统性能。
2.2 时间复杂度与空间复杂度 ⭐⭐⭐⭐⭐
1. 时间复杂度
设算法 $A$ 在输入 $I$ 上运行,问题规模为 $n$,则运行时间可记作:
常见分析角度:
| 类型 | 含义 |
|---|---|
| 最好时间复杂度 | 对规模为 $n$ 的所有输入,运行时间最小 |
| 最坏时间复杂度 | 对规模为 $n$ 的所有输入,运行时间最大 |
| 平均时间复杂度 | 对规模为 $n$ 的输入按概率求期望 |
考试默认:若题目未特别说明,一般分析最坏情况时间复杂度。
2. 程序步
程序步是语法或语义上有意义、且执行时间与问题规模无关的程序段。
引入程序步是为了简化算法的事前分析。
PPT例子:
1 | float Sum(float list[], const int n) { |
程序步计数结果:
递归求和:
1 | float RSum(float list[], const int n) { |
递推式:
解得:
3. 空间复杂度
算法运行所需空间包括:
| 空间类型 | 含义 |
|---|---|
| 固定空间需求 | 与输入规模无关,如代码、常量、简单变量 |
| 可变空间需求 | 与输入规模相关,如数组、递归栈、动态分配空间 |
2.3 渐近表示法 ⭐⭐⭐⭐⭐
1. 大 O 记号:渐进上界
若存在正常数 $c,n_0$,使得当 $n\ge n_0$ 时:
则:
例:证明 $10n^2+4n+2=O(n^2)$。
当 $n\ge 5$ 时:
取 $c=11,n_0=5$ 即可。
2. 大 $\Omega$ 记号:渐进下界
若存在正常数 $c,n_0$,使得当 $n\ge n_0$ 时:
则:
3. 大 $\Theta$ 记号:渐进紧确界
若存在正常数 $c_1,c_2,n_0$,使得当 $n\ge n_0$ 时:
则:
4. 小 o 记号:严格低阶
表示 $f(n)$ 比 $g(n)$ 增长严格慢。等价地:
5. 多项式定理
若:
则:
也自然有:
6. 常见复杂度大小顺序
多项式时间:
指数级/超多项式:
易错点:$n!$ 的增长速度比 $2^n$ 快,但比 $n^n$ 慢。
2.4 递推关系与主方法 ⭐⭐⭐⭐⭐
1. 递推方程
递推方程使用一个或多个更小规模的函数值描述 $T(n)$,必须有初始条件。
常见解法:
| 方法 | 适合题型 |
|---|---|
| 替换法 | 先猜答案,再用归纳法证明 |
| 迭代法 | 不断展开递推式,化为求和 |
| 递归树法 | 分层统计每层代价 |
| 主方法 | 形如 $T(n)=aT(n/b)+f(n)$ |
2. 主方法
递推式:
其中 $a\ge1,b>1$。
令:
比较 $f(n)$ 与 $n^E$:
| 情况 | 条件 | 结论 |
|---|---|---|
| 情况1 | $f(n)=O(n^{E-\varepsilon})$ | $T(n)=\Theta(n^E)$ |
| 情况2 | $f(n)=\Theta(n^E\log^k n)$ | $T(n)=\Theta(n^E\log^{k+1}n)$ |
| 情况3 | $f(n)=\Omega(n^{E+\varepsilon})$ 且满足正则条件 | $T(n)=\Theta(f(n))$ |
正则条件通常写作:
3. PPT典型例题
$T(n)=16T(n/4)+n$
$a=16,b=4,E=\log_4 16=2$,$f(n)=n=O(n^{2-1})$。
所以:$T(n)=T(3n/7)+1$
可看作 $a=1,b=7/3,E=0$,$f(n)=1=\Theta(n^0)$。
所以:$T(n)=3T(n/4)+n\log n$
$E=\log_4 3<1$,$n\log n$ 更大,且满足正则条件。
所以:$T(n)=2T(n/2)+n\log n$
$E=1$,$f(n)=n\log n=\Theta(n^E\log n)$。
所以:
第2章练习题与答案
练习2-1
判断:$3n^2+2n+1=\Theta(n^2)$。
答案:正确。因为当 $n\ge1$ 时:
取 $c_1=3,c_2=6,n_0=1$ 即可。
练习2-2
求递推式 $T(n)=T(n/2)+1$ 的复杂度。
答案:
每次规模减半,展开约 $\log_2 n$ 层,每层代价为 1。
练习2-3
用主方法求 $T(n)=4T(n/2)+n$。
答案:
$a=4,b=2,E=\log_2 4=2$,$f(n)=n=O(n^{2-1})$,属于情况1。
练习2-4
按增长速度从小到大排序:$n^2,\log n,n!,n\log n,2^n,n^3$。
答案:
练习2-5
递归算法的空间复杂度为什么通常要考虑递归栈?
答案:每次递归调用都会保存返回地址、局部变量和参数等信息。若递归深度为 $d$,每层额外空间为 $O(1)$,则递归栈空间为 $O(d)$。
第3章 伸展树与跳表 ⭐⭐⭐
3.1 字典与二叉搜索树 ⭐⭐⭐
字典是词条集合,词条包含关键字和其他信息。常见操作:
| 操作 | 含义 |
|---|---|
| Search | 按关键字查找元素 |
| Insert | 插入新元素,若关键字已存在则 Duplicate |
| Remove | 删除指定关键字元素 |
字典可用线性表、散列表、搜索树、伸展树、跳表等结构表示。
二叉搜索树(BST)定义:
- 根的左子树所有结点关键字小于根;
- 根的右子树所有结点关键字大于根;
- 左右子树也都是二叉搜索树。
BST的缺点:若插入序列有序,树会退化为链表,查找、插入、删除最坏为 $O(n)$。
3.2 伸展树 Splay Tree ⭐⭐⭐⭐
1. 基本思想
伸展树是一棵自调节二叉搜索树。每访问一个元素后,将该元素移动到根部,这个过程叫伸展。
目的:经常访问的元素靠近根,提高平均访问效率。
PPT结论:
在伸展树上执行 $m$ 次搜索、插入、删除,总时间为 $O(m\log n)$,平均分摊代价为 $O(\log n)$。
2. 哪个结点作为伸展结点?
| 操作 | 伸展结点 |
|---|---|
| 搜索成功 | 被找到的结点 |
| 插入成功 | 新插入的结点 |
| 删除成功 | 被删除结点的双亲 |
| 操作失败 | 搜索路径上遇到的最后一个结点 |
3. 旋转类型
| 情况 | 名称 | 含义 |
|---|---|---|
| 父结点是根,且当前结点是左孩子 | zig | 右旋 |
| 父结点是根,且当前结点是右孩子 | zag | 左旋 |
| 当前结点与父结点同为左孩子 | zig-zig | 先祖父右旋,再父右旋 |
| 当前结点与父结点同为右孩子 | zag-zag | 先祖父左旋,再父左旋 |
| 当前结点是右孩子,父结点是左孩子 | zig-zag | 先父左旋,再祖父右旋 |
| 当前结点是左孩子,父结点是右孩子 | zag-zig | 先父右旋,再祖父左旋 |
4. 旋转次数
若伸展结点到根路径长度为 $k$:
- $k$ 为偶数:执行 $k/2$ 次双重旋转;
- $k$ 为奇数:执行 $\lfloor k/2\rfloor$ 次双重旋转,再执行一次单旋。
3.3 跳表 Skip List ⭐⭐⭐
1. 为什么需要跳表?
有序数组可以二分搜索,但链表不能直接访问中间结点。
跳表在链表上增加多级索引,可替代平衡搜索树,获得良好的运算性能。
2. 跳表特征
- 由若干层组成;
- 第一层包含所有元素;
- 每一层都是有序链表;
- 若元素 $x$ 出现在第 $i$ 层,则所有比 $i$ 小的层都包含 $x$;
- 第 $i$ 层元素通过
down指针指向下一层相同值元素; - 每层通常包含 $-\infty,+\infty$ 哨兵;
top指针指向最高层第一个元素。
3. 查找过程
从最高层开始:
1 | p = top; |
查找策略:能向右就向右,不能向右就向下。
第3章练习题与答案
练习3-1
伸展树为什么属于自调节搜索树?
答案:因为它不显式维护平衡因子或颜色,而是在每次访问后通过旋转将访问结点移到根部,使近期频繁访问的结点更靠近根,从而获得较好的分摊性能。
练习3-2
搜索失败时,伸展树应伸展哪个结点?
答案:搜索过程中遇到的最后一个结点。
练习3-3
判断旋转类型:当前结点是父结点的左孩子,父结点也是祖父结点的左孩子。
答案:zig-zig,属于同向双旋。
练习3-4
跳表查找时为什么从最高层开始?
答案:最高层元素最少,能快速跳过大量不可能的元素;逐层下降后在底层定位,类似链表上的二分思想。
练习3-5
伸展树的单次操作最坏时间一定是 $O(\log n)$ 吗?
答案:不一定。单次操作最坏可能为 $O(n)$,但连续 $m$ 次操作的总时间为 $O(m\log n)$,即分摊 $O(\log n)$。
第4章 基本搜索和遍历方法 ⭐⭐⭐⭐
4.1 基本概念 ⭐⭐⭐
搜索分为:
| 类型 | 含义 | 代表算法 |
|---|---|---|
| 无知搜索 | 盲目、穷举,不利用启发信息 | DFS、BFS、D-搜索 |
| 有知搜索 | 使用启发式信息 | A* 等 |
图遍历常用颜色:
| 颜色 | 含义 |
|---|---|
| White | 未访问 |
| Gray | 已发现但未检测完成,活结点 |
| Black | 已检测完成,死结点 |
邻接表结构:
1 | struct ENode { |
4.2 BFS 广度优先搜索 ⭐⭐⭐⭐⭐
1. 基本思想
BFS从起点开始,先访问距离起点最近的结点,再逐层向外扩展。使用队列维护活结点。
1 | BFS(u): |
2. 考试重点
- BFS生成的是广度优先树/森林;
- 对无权图,BFS能求源点到其他结点的最短路径长度;
- 邻接表下时间复杂度为 $O(n+e)$;
- 空间复杂度通常为 $O(n)$。
4.3 DFS 深度优先搜索 ⭐⭐⭐⭐⭐
1. 基本思想
DFS从一个结点出发,沿一条路径尽可能深入,无法继续时回溯。常用递归或栈实现。
1 | DFS(u): |
其中:
- $d[u]$:发现时间;
- $f[u]$:完成时间。
2. 括号定理
对任意两个结点 $u,v$,区间 $[d[u],f[u]]$ 与 $[d[v],f[v]]$ 只有三种关系之一:
- 完全分离:互不为后裔;
- $[d[u],f[u]]$ 包含 $[d[v],f[v]]$:$v$ 是 $u$ 的后裔;
- $[d[v],f[v]]$ 包含 $[d[u],f[u]]$:$u$ 是 $v$ 的后裔。
推论:
3. 白色路径定理
在时刻 $d[u]$,若存在从 $u$ 到 $v$ 的路径,并且路径上除 $u$ 外均为白色,则 $v$ 是 DFS 森林中 $u$ 的后裔。
4. DFS边分类
| 边类型 | 说明 |
|---|---|
| 树边 | DFS森林中的边,通常从灰到白 |
| 反向边 | 从后裔指向祖先,环也看作反向边 |
| 正向边 | 从祖先指向后裔的非树边 |
| 交叉边 | 其他边 |
重要性质:
- 有向图无回路,当且仅当 DFS 中不包含反向边;
- 无向图的 DFS 森林只包含树边和反向边。
4.4 双连通分量与关节点 ⭐⭐⭐⭐
1. 基本概念
| 概念 | 定义 |
|---|---|
| 关节点 | 删除该结点及相关边后,图分裂成两个或多个非空分图 |
| 双连通图 | 无向连通图不含关节点 |
| 双连通分量 | 最大双连通子图 |
等价关系:
无向连通图是双连通图 $\iff$ 任意两个结点之间存在简单回路 $\iff$ 图中不含关节点。
2. 关节点判定
对无向图做DFS,定义:
其中:
- $w$ 是 $u$ 的孩子;
- $(u,x)$ 是从 $u$ 出发的反向边。
判定规则:
| 结点类型 | 成为关节点的条件 |
|---|---|
| 根结点 | 至少有两个孩子 |
| 非根结点 $u$ | 存在孩子 $w$ 使得 $Low[w]\ge d[u]$ |
3. 双连通分量求法
DFS过程中把边压栈。
当访问到树边 $(u,v)$ 且满足:
则从栈中弹出边,直到弹出 $(u,v)$,这些边组成一个新的双连通分量。
第4章练习题与答案
练习4-1
BFS和DFS分别使用什么数据结构维护活结点?
答案:BFS使用队列;DFS通常使用递归栈或显式栈。
练习4-2
若 $d[u]=2,f[u]=9,d[v]=4,f[v]=7$,则 $u,v$ 在DFS树中是什么关系?
答案:因为 $2<4<7<9$,所以 $v$ 是 $u$ 的后裔。
练习4-3
有向图中存在反向边说明什么?
答案:说明图中存在有向环。因此有向图无环当且仅当DFS中没有反向边。
练习4-4
根结点成为关节点的条件是什么?
答案:DFS树根至少有两个孩子。
练习4-5
非根结点 $u$ 有孩子 $w$,若 $Low[w]\ge d[u]$,为什么 $u$ 是关节点?
答案:说明以 $w$ 为根的子树不能通过反向边到达 $u$ 的祖先。删除 $u$ 后,该子树与图的其余部分断开,因此 $u$ 是关节点。
第5章 分治法 ⭐⭐⭐⭐⭐
5.1 分治法基本思想 ⭐⭐⭐⭐⭐
一个问题可用分治法求解,通常满足:
- 能分解成若干个规模较小、相互独立、与原问题类型相同的子问题;
- 子问题足够小时能直接求解;
- 能将子问题的解组合成原问题的解。
通用模板:
1 | Solution DandC(Problem P) { |
常见递推:
结论:
| 比较 | 复杂度 |
|---|---|
| $a>b^k$ | $\Theta(n^{\log_b a})$ |
| $a=b^k$ | $\Theta(n^k\log n)$ |
| $a<b^k$ | $\Theta(n^k)$ |
5.2 分治法求最大最小元 ⭐⭐⭐⭐
普通扫描同时求最大最小值:
次比较。
分治法:
- 若只有一个元素,最大值=最小值;
- 若两个元素,只比较一次;
- 否则左右递归求最大最小,再合并。
当 $n=2^k$ 时,比较次数:
比普通方法少。
5.3 二分搜索 ⭐⭐⭐⭐⭐
问题:在有序表中查找元素 $x$。
递推式:
复杂度:
二叉判定树性质:
- 成功搜索比较次数不超过 $\lfloor\log n\rfloor+1$;
- 不成功搜索需要 $\lfloor\log n\rfloor$ 或 $\lfloor\log n\rfloor+1$ 次比较;
- 基于比较的搜索最坏至少需要 $\lfloor\log n\rfloor+1$ 次比较。
5.4 归并排序 ⭐⭐⭐⭐⭐
思想:
- Divide:序列一分为二;
- Conquer:递归排序左右子序列;
- Combine:线性时间合并两个有序序列。
递推式:
复杂度:
空间复杂度:
5.5 快速排序 ⭐⭐⭐⭐⭐
1. 分划思想
选择主元 pivot,将序列分为:
- 左边元素 $\le pivot$;
- 右边元素 $\ge pivot$。
然后递归排序左右子序列。
2. 复杂度
| 情况 | 递推 | 复杂度 |
|---|---|---|
| 最好 | $T(n)=2T(n/2)+\Theta(n)$ | $\Theta(n\log n)$ |
| 平均 | 近似均衡分划 | $\Theta(n\log n)$ |
| 最坏 | $T(n)=T(n-1)+\Theta(n)$ | $O(n^2)$ |
3. 改进方法
- 随机选择主元;
- 取首、中、尾三者中值作为主元;
- 子序列足够小时改用插入排序;
- 将递归算法改为非递归算法。
4. 排序下界
任何基于关键字比较的排序算法,最坏情况下至少需要:
次比较。
5.6 选择问题 ⭐⭐⭐⭐
选择问题:在 $n$ 个元素中找第 $k$ 小元素。
1. 基于Partition的选择
分划后设左子表含主元的长度为 $p$:
- 若 $k=p$,主元就是第 $k$ 小;
- 若 $k<p$,在左子表找第 $k$ 小;
- 若 $k>p$,在右子表找第 $k-p$ 小。
随机主元:
- 平均时间 $O(n)$;
- 最坏时间 $O(n^2)$。
2. 线性时间选择
采用“中位数的中位数”选择主元,递推可写成:
结论:
5.7 Strassen矩阵乘法 ⭐⭐
普通分治需要 8 次子矩阵乘法:
Strassen将乘法减少为 7 次:
记住结论即可,公式不一定全背,但应知道“7次乘法代替8次乘法”。
第5章练习题与答案
练习5-1
分治法适用的三个条件是什么?
答案:可分解为相互独立的同类子问题;小规模子问题可直接求解;子问题解能合并为原问题解。
练习5-2
二分搜索的递推式和复杂度是什么?
答案:
练习5-3
归并排序和快速排序的Combine步骤分别是什么?
答案:归并排序的Combine是线性合并两个有序子序列;快速排序的Combine是空操作,因为分划后左右递归排好即整体有序。
练习5-4
快速排序最坏情况何时出现?
答案:每次主元都选到当前序列最小或最大元素,导致规模分解为 $0$ 和 $n-1$,复杂度退化为 $O(n^2)$。
练习5-5
求最大最小元,普通扫描和分治法比较次数分别是多少?
答案:普通扫描为 $2(n-1)$;当 $n=2^k$ 时,分治法为 $3n/2-2$。
练习5-6
Strassen算法为什么比普通矩阵乘法快?
答案:普通分治每层需要8次规模为 $n/2$ 的矩阵乘法,Strassen只需要7次,递推指数从3降为 $\log_2 7\approx2.81$。
第6章 贪心法 ⭐⭐⭐⭐⭐
6.1 贪心法一般方法 ⭐⭐⭐⭐⭐
贪心法用于求解最优化问题。
每一步做当前看起来最优的选择,希望最终得到整体最优解。
基本概念:
| 概念 | 含义 |
|---|---|
| 可行解 | 满足约束条件的解 |
| 目标函数 | 衡量解优劣的函数 |
| 最优解 | 使目标函数取最大或最小的可行解 |
| 最优量度标准 | 每一步选择依据,也称贪心准则 |
| 可行性判定函数 | 判断加入新元素后是否仍可行 |
通用伪代码:
1 | Solution Greedy(A, n) { |
贪心法设计步骤:
- 将问题看作一系列决策;
- 确定每一步的局部最优准则;
- 证明局部最优能导出全局最优。
必背:贪心算法不一定总能得到最优解,必须证明贪心选择性质和最优子结构。
6.2 背包问题 ⭐⭐⭐⭐⭐
1. 一般背包问题(可分割)
解向量:
约束:
目标:
贪心准则:
按单位重量收益 $p_i/w_i$ 非增排序,优先装单位价值高的物品。
复杂度主要来自排序:
定理:若
则按该顺序贪心装入得到最优解。
2. 0/1背包问题(不可分割)
$x_i\in{0,1}$,物品只能整件选或不选。
贪心法一般不能保证最优,因为物品不可分割,可能造成剩余容量浪费。
考试常问:一般背包可用贪心,0/1背包通常用动态规划、回溯或分枝限界。
6.3 带时限的作业排序 ⭐⭐⭐⭐
1. 问题描述
单机系统,每个作业运行时间均为1。
作业 $i$ 有截止期限 $d_i$ 和收益 $p_i$。
若作业在期限内完成,则获得收益。目标是最大化总收益。
2. 贪心策略
- 按收益 $p_i$ 非增排序;
- 依次考虑作业;
- 若加入后仍能在截止期限内完成,则选择该作业。
可行性判定:
将已选作业按截止期限非降排序,若第 $j$ 个作业满足:
则该排序可行。
3. 并查集改进
朴素可行性检查可达 $O(n^2)$。
使用并查集记录可用时间片,可将时间接近降到线性级别。
6.4 最佳合并模式 ⭐⭐⭐⭐
问题:将 $n$ 个长度不同的有序子文件两两合并成一个文件,使总读写记录数最少。
贪心准则:
每次选择两个长度最小的文件合并。
这与哈夫曼树完全对应:
- 文件长度 = 外结点权值;
- 合并代价 = 带权外路径长度;
- 最优合并模式 = 最优二叉合并树/哈夫曼树。
K路合并:
- 每次选 $K$ 个最小权值合并;
- 若 $(n-1)\bmod(K-1)\ne0$,需补充若干零权值虚结点;
- 补充数量:
最多 $K-2$ 个。
6.5 最小代价生成树 MST ⭐⭐⭐⭐⭐
1. 概念
无向连通图的生成树:包含全部结点,边数为 $n-1$,且连通无环。
最小代价生成树:所有生成树中边权和最小者。
2. MST性质(割性质)
设 $U$ 是 $V$ 的真子集,若边 $(u,v)$ 是所有连接 $U$ 与 $V-U$ 的边中权值最小者,则存在一棵MST包含该边。
Prim和Kruskal都基于这一性质。
3. Prim算法
贪心准则:
每次选一条连接“树内结点”和“树外结点”的最小边。
适合稠密图。
邻接矩阵实现复杂度:
4. Kruskal算法
贪心准则:
按边权从小到大选边,只要不形成回路就加入。
使用并查集判断回路。
复杂度:
6.6 Dijkstra单源最短路径 ⭐⭐⭐⭐⭐
1. 适用条件
带权有向图或无向图,边权必须非负。
若存在负权边,Dijkstra不能保证正确。
2. 核心数据结构
| 数组 | 含义 |
|---|---|
d[i] |
源点 $s$ 到结点 $i$ 的当前最短路径长度 |
path[i] |
当前最短路径上 $i$ 的前驱 |
inS[i] |
结点 $i$ 是否已加入最短路径集合 $S$ |
3. 算法步骤
- 初始化 $S={s}$;
- 在 $V-S$ 中选择 $d[k]$ 最小的结点 $k$ 加入 $S$;
- 用 $k$ 松弛其他结点:
若更新,则:
- 重复直到所有可达结点处理完。
邻接矩阵实现复杂度:
6.7 磁带最优存储 ⭐⭐⭐
1. 单带最优存储
有 $n$ 个程序,长度为 $a_i$,每次检索前磁带都倒回最前端。
若检索概率相等,平均检索时间与程序排列的前缀长度和有关。
贪心准则:
按程序长度非降序存放。
即短程序放前面,使平均检索时间最小。
2. 多带最优存储
有 $m$ 条磁带,先按程序长度非降序排序,然后轮流分配:
这样可使总检索代价最小。
6.8 贪心法证明模板 ⭐⭐⭐⭐⭐
贪心正确性通常证明两点:
- 贪心选择性质:存在一个最优解包含当前贪心选择;
- 最优子结构:做出贪心选择后,剩余问题的最优解能与当前选择组合成原问题最优解。
常用证明方法:
1 | ① 设 O 是任意一个最优解。 |
第6章练习题与答案
练习6-1
为什么0/1背包不能直接用单位价值贪心保证最优?
答案:因为物品不可分割,单位价值最高的选择可能占用容量后留下无法利用的剩余空间,导致总收益不如其他组合。
练习6-2
一般背包中,$M=20$,物品重量 $(18,15,10)$,收益 $(25,24,15)$。按贪心法求最优收益。
答案:单位价值分别为 $25/18\approx1.39$、$24/15=1.6$、$15/10=1.5$。排序为物品1、物品2、物品0。
先取物品1:重量15,收益24,剩余5。
再取物品2的一半:收益 $15\times 5/10=7.5$。
总收益:
练习6-3
带时限作业排序为什么要按收益非增顺序考虑?
答案:目标是最大化收益。按收益从高到低尝试,若可行则保留。正确性可用交换论证证明:若某最优解不含当前可行的高收益作业,可用该作业替换某个低收益作业且不降低总收益。
练习6-4
文件长度为 $5,10,20,30$,求两路最佳合并总代价。
答案:每次合并最小两个:
- $5+10=15$;
- $15+20=35$;
- $30+35=65$。
总代价:
练习6-5
Prim和Kruskal分别适合什么图?
答案:Prim用邻接矩阵时适合稠密图;Kruskal按边排序,适合稀疏图,常配合并查集。
练习6-6
Dijkstra为什么要求边权非负?
答案:Dijkstra一旦选择当前 $d$ 最小的结点加入 $S$,就认为其最短距离已确定。若存在负权边,之后可能通过负边得到更短路径,破坏该贪心选择的正确性。
练习6-7
多带最优存储如何分配程序?
答案:先按程序长度非降序排序,再将程序 $i$ 放到第 $i\bmod m$ 条磁带上。
第7章 动态规划法 ⭐⭐⭐⭐⭐
7.1 动态规划基本要素 ⭐⭐⭐⭐⭐
动态规划适合求解具有以下两个性质的问题:
| 性质 | 含义 |
|---|---|
| 最优子结构 | 原问题最优解包含子问题最优解 |
| 重叠子问题 | 不同子问题会反复用到相同的小子问题 |
动态规划基本步骤:
- 刻画最优解的结构特性;
- 递归定义最优解值;
- 自底向上计算最优解值;
- 根据计算信息构造一个最优解。
DP vs 分治 vs 贪心
| 方法 | 子问题关系 | 是否依赖子问题解做选择 | 常见实现 |
|---|---|---|---|
| 分治 | 子问题相互独立 | 不一定 | 递归 |
| 贪心 | 每步只看当前最优 | 不依赖未来和子问题解 | 迭代 |
| 动态规划 | 子问题重叠 | 依赖子问题解 | 表格/备忘录 |
7.2 多段图问题 ⭐⭐⭐
多段图是带权有向图,结点分成 $k$ 段,边只从第 $i$ 段指向第 $i+1$ 段。目标是求源点 $s$ 到汇点 $t$ 的最短路径。
定义:
递推思想:
边界:
同时记录决策 $D(i,j)$,用于回溯构造最短路径。
7.3 每对结点间最短路径 ⭐⭐⭐
PPT中该节标注“略”,但若考试涉及,通常考 Floyd 算法。
状态:
表示只允许经过编号不超过 $k$ 的中间结点时,$i$ 到 $j$ 的最短路径。
递推:
复杂度:
7.4 矩阵连乘 ⭐⭐⭐⭐⭐
1. 问题
给定矩阵序列:
矩阵乘法满足结合律,不同加括号方式会导致不同数乘次数。目标是找最少数乘次数。
2. 状态定义
3. 递推式
若最后一次断开在 $k$:
边界:
记录:
用于构造最优加括号方案。
复杂度:
空间:
4. 备忘录方法
备忘录方法是自顶向下的动态规划:
递归求解子问题,并把已经求过的 $m[i][j]$ 存起来,避免重复计算。
7.5 最长公共子序列 LCS ⭐⭐⭐⭐⭐
1. 定义
序列 $Z$ 是 $X$ 的子序列:存在严格递增下标序列,使 $Z$ 中元素按顺序出现在 $X$ 中。
LCS是两个序列的最长公共子序列。
2. 状态定义
3. 递推式
边界:
若:
则:
否则:
记录方向:
| 方向 | 含义 |
|---|---|
| 左上 | 当前字符匹配,输出该字符 |
| 上 | 来自 $c[i-1][j]$ |
| 左 | 来自 $c[i][j-1]$ |
复杂度:
7.6 最优二叉搜索树 OBST ⭐⭐⭐
1. 问题
给定有序关键字:
成功搜索概率为 $p(i)$,失败搜索概率为 $q(i)$。
目标是构造平均搜索时间最小的二叉搜索树。
2. DP思想
若 $a_k$ 为根,则:
- 左子树包含 $ai,\dots,a{k-1}$;
- 右子树包含 $a_{k+1},\dots,a_j$。
状态:
- $c[i][j]$:子树的最小成本;
- $w[i][j]$:概率权值和;
- $r[i][j]$:最优根。
递推核心:
7.7 0/1背包动态规划 ⭐⭐⭐⭐⭐
1. 问题
物品不可分割:
目标:
2. 离散容量DP
定义:
递推:
若 $j\ge w_i$:
若 $j<w_i$:
边界:
复杂度:
3. 连续数据/阶跃点方法
PPT中还提到阶跃点集合:
- $S_{-1}={(0,0)}$;
- $Si$ 由 $S{i-1}$ 与加入第 $i$ 件物品后的集合合并;
- 删除被支配点和 $X>M$ 的点。
最坏情况下时间和空间复杂度均可达:
7.8 流水作业调度 ⭐⭐
PPT目录列出“流水作业调度”,正文未展开。若课堂补充,常见考法是两台机器流水作业的 Johnson 法则:
- 对每个作业有两台机器加工时间 $a_i,b_i$;
- 若 $\min(a_i,b_i)=a_i$,尽量排前;
- 若 $\min(a_i,b_i)=b_i$,尽量排后;
- 目标常是最小化总完成时间。
本节建议作为了解题,重点仍是矩阵连乘、LCS、0/1背包。
第7章练习题与答案
练习7-1
动态规划适用的两个基本要素是什么?
答案:最优子结构和重叠子问题。
练习7-2
动态规划和贪心法的主要区别是什么?
答案:贪心每步选择只依赖当前状态,不依赖子问题解;动态规划的选择依赖相关子问题的最优解,因此通常先求子问题,再决定最优选择。
练习7-3
写出矩阵连乘的状态和递推式。
答案:
边界 $m[i][i]=0$。
练习7-4
若 $X=ABCBDAB$,$Y=BDCABA$,LCS长度是多少?举出一个LCS。
答案:LCS长度为4。一个LCS是 BCBA,另一个常见答案是 BDAB。
练习7-5
写出0/1背包DP递推式。
答案:
若 $j\ge w_i$:
否则:
练习7-6
为什么矩阵连乘具有最优子结构?
答案:若 $Ai\cdots A_j$ 的最优加括号方式最后在 $k$ 处分开,则左边 $A_i\cdots A_k$ 和右边 $A{k+1}\cdots A_j$ 必须分别也是最优的,否则替换成更优子结构会得到更少乘法次数,矛盾。
练习7-7
Floyd算法的时间复杂度是多少?
答案:三重循环,时间复杂度为 $O(n^3)$。
第8章 回溯法 ⭐⭐⭐⭐
8.1 回溯法一般方法 ⭐⭐⭐⭐⭐
回溯法适合解可表示为 $n$ 元组的问题:
其中每个 $x_i$ 取自有限集合 $S_i$。
1. 基本概念
| 概念 | 含义 |
|---|---|
| 显式约束 | 规定每个 $x_i$ 可取值范围 |
| 隐式约束 | 判断候选解是否可行 |
| 解空间 | 所有满足显式约束的候选解集合 |
| 状态空间树 | 描述解空间的树结构 |
| 解状态 | 从根到该结点路径代表一个候选解 |
| 答案状态 | 从根到该结点路径代表一个可行解 |
| 目标函数/代价函数 | 衡量可行解优劣 |
2. 剪枝函数
| 剪枝函数 | 用途 |
|---|---|
| 约束函数 | 剪去不满足约束的子树 |
| 限界函数 | 剪去不可能产生最优解的子树 |
二者统称为剪枝函数。
3. 回溯法 vs 分枝限界法
| 方法 | 生成方式 | 常用场景 |
|---|---|---|
| 回溯法 | 深度优先生成状态空间树 | 找可行解或所有解 |
| 分枝限界法 | 广度优先或最小代价优先生成 | 找最优解 |
4. 递归回溯模板
1 | void RBacktrack(int k) { |
5. 复杂度
回溯算法最坏情况可能达到:
其中 $p(n)$ 是生成一个结点所需的多项式时间。
8.2 n皇后问题 ⭐⭐⭐⭐⭐
1. 问题
在 $n\times n$ 棋盘上放置 $n$ 个皇后,使任意两个皇后不在同一行、同一列、同一斜线。
2. 解表示
用 $n$ 元组:
其中 $x_i$ 表示第 $i$ 行皇后所在列。
显式约束:每行选一列。
若不允许同列,则状态空间为排列树,叶子数为 $n!$。
3. 冲突判定
两个皇后在 $(i,j)$ 和 $(k,l)$:
- 同列:$j=l$;
- 同一斜线:
Place函数核心:
1 | bool Place(int k) { |
8.3 子集和数问题 ⭐⭐⭐⭐
1. 问题
给定 $n$ 个不同正数 $w_i$ 和目标 $M$,求所有子集,使元素和为 $M$。
2. 解表示
固定长度:
$x_i=1$ 表示选择 $w_i$。
也可以用可变长度元组记录被选元素下标或值。
3. 剪枝思想
设当前和为 $s$,剩余和为 $r$,下一个元素为 $w_k$:
- 若 $s=M$,输出答案;
- 若 $s+w_k>M$,选 $w_k$ 可能越界;
- 若 $s+r<M$,即使全选也达不到 $M$,剪枝。
8.4 图的m着色 ⭐⭐⭐⭐
1. 问题
给定无向图 $G=(V,E)$ 和 $m$ 种颜色,问是否能给每个结点着一种颜色,使任意相邻结点颜色不同。
2. 解表示
$x_i$ 表示结点 $i$ 的颜色。
显式约束:$x_i$ 取颜色集合。
隐式约束:
解空间大小:
8.5 哈密顿环 ⭐⭐⭐
1. 问题
给定 $n$ 个结点的连通图,找一条回路,它从某结点出发,经过每个结点恰好一次,最后回到起点。
2. 解表示
$x_i$ 表示路径上的第 $i$ 个结点。
约束:
- $x_i$ 互不相同;
- $(xi,x{i+1})\in E$;
- $(x_{n-1},x_0)\in E$。
8.6 回溯法求0/1背包 ⭐⭐⭐⭐
0/1背包是最优化问题,除约束函数外还需要限界函数。
1. 约束函数
若当前重量 $cw$,当前考虑物品 $k$:
则可以进入左子树(选择物品 $k$)。
2. 限界函数
设当前收益为 $cp$,当前已知最优收益下界为 $L$ 或 fp。
用可分割背包的贪心方法估计当前结点可能达到的收益上界 $bp$。
若:
则该结点子树不可能产生更优解,可以剪枝。
PPT提醒:通常要求物品按单位价值 $p_i/w_i$ 非增排序。
8.7 批处理作业调度 ⭐⭐⭐
1. 问题
有 $n$ 个作业,每个作业需要依次在两台设备 $P_1,P_2$ 上加工。
作业 $i$ 在两台设备上的处理时间分别为 $a_i,b_i$。
目标是找到调度方案,使所有作业完成时间之和最小。
2. 回溯求解
解空间:作业的排列树,共 $n!$ 个叶子。
用当前部分调度的完成时间和进行剪枝:
若当前部分排列的完成时间和已经超过当前最优上界 $U$,则剪去该分枝。
第8章练习题与答案
练习8-1
什么是显式约束和隐式约束?
答案:显式约束规定每个分量的取值范围,形成候选解空间;隐式约束判断候选解是否满足问题要求、是否为可行解。
练习8-2
回溯法和分枝限界法的主要区别是什么?
答案:回溯法通常深度优先生成状态空间树;分枝限界法通常广度优先或按优先级生成结点。回溯多用于找可行解/全部解,分枝限界多用于最优化问题。
练习8-3
n皇后中,若两个皇后在 $(i,j)$ 和 $(k,l)$,如何判断是否在同一斜线?
答案:
练习8-4
图m着色的隐式约束是什么?
答案:若边 $(i,j)\in E$,则两个端点颜色不同,即 $x_i\ne x_j$。
练习8-5
哈密顿环和普通环的区别是什么?
答案:哈密顿环必须经过图中每个结点恰好一次,并最后回到起点;普通环没有这个要求。
练习8-6
0/1背包回溯中限界函数的作用是什么?
答案:估计当前结点子树可能达到的最大收益上界。若上界仍小于当前已知最优值,则该子树不可能产生最优解,可剪枝。
练习8-7
批处理作业调度的解空间通常是什么树?
答案:排列树,因为一个调度方案对应作业的一种排列。
综合练习题库(含答案)⭐⭐⭐⭐⭐
一、选择/判断题
1. 判断:贪心算法只要每一步选当前最优,就一定能得到全局最优。
答案:错误。必须证明贪心选择性质和最优子结构。
2. 判断:动态规划一定要求子问题相互独立。
答案:错误。子问题相互独立更适合分治;动态规划适用于重叠子问题。
3. 判断:Dijkstra算法可以处理负权边。
答案:错误。Dijkstra要求边权非负。
4. 判断:无向图DFS中只会出现树边和反向边。
答案:正确。
5. 判断:快速排序最坏时间复杂度为 $O(n\log n)$。
答案:错误。最坏为 $O(n^2)$。
二、计算题
1. 主方法
求:
答案:
$a=9,b=3,E=\log_3 9=2$,$f(n)=n^2=\Theta(n^E)$。
2. 最佳合并模式
文件长度为 $2,3,5,7,11$,求最优合并总代价。
答案:
- $2+3=5$;
- $5+5=10$;
- $7+10=17$;
- $11+17=28$。
总代价:
3. 0/1背包DP
容量 $M=5$,物品重量 $(2,3,4)$,收益 $(3,4,5)$,最大收益是多少?
答案:
可行组合:
- 物品1:收益3;
- 物品2:收益4;
- 物品3:收益5;
- 物品1+2:重量5,收益7。
最大收益为:
三、简答题
1. 写出DP算法设计四步骤。
答案:
- 刻画最优解结构;
- 递归定义最优解值;
- 自底向上计算最优解值;
- 根据记录信息构造最优解。
2. 写出贪心正确性的常用证明思路。
答案:设任一最优解,若不含贪心选择,则用贪心选择替换其中某个元素,证明替换后仍可行且目标值不变差。因此存在包含贪心选择的最优解,再对子问题归纳证明。
3. 回溯法为什么能减少搜索量?
答案:回溯法在生成状态空间树时使用约束函数和限界函数剪枝,提前排除不可能产生可行解或最优解的子树,避免穷举全部候选解。
4. 分治法和动态规划的本质区别是什么?
答案:分治法将问题划分为相互独立的子问题,分别递归求解再合并;动态规划适用于子问题重叠的情况,通过保存子问题结果避免重复计算。
5. MST性质是什么?
答案:设 $U$ 是 $V$ 的真子集,若边 $(u,v)$ 是所有连接 $U$ 与 $V-U$ 的边中权值最小者,则存在一棵最小生成树包含该边。
四、算法设计模板题
1. 用动态规划设计算法时,答题模板是什么?
答案:
1 | ① 定义状态:dp[i][j] 表示什么; |
2. 用回溯法设计算法时,答题模板是什么?
答案:
1 | ① 说明解向量形式; |
3. 用贪心法设计算法时,答题模板是什么?
答案:
1 | ① 说明问题的解结构和目标函数; |
期末最后背诵版 ⭐⭐⭐⭐⭐
1. 必背复杂度
| 算法 | 时间复杂度 |
|---|---|
| 二分搜索 | $\Theta(\log n)$ |
| 归并排序 | $\Theta(n\log n)$ |
| 快速排序 | 平均 $\Theta(n\log n)$,最坏 $O(n^2)$ |
| 分治最大最小 | 比较次数 $3n/2-2$ |
| 随机选择 | 平均 $O(n)$,最坏 $O(n^2)$ |
| 线性选择 | $O(n)$ |
| Strassen | $\Theta(n^{2.81})$ |
| Prim邻接矩阵 | $O(n^2)$ |
| Kruskal | $O(e\log e)$ |
| Dijkstra邻接矩阵 | $O(n^2)$ |
| Floyd | $O(n^3)$ |
| 矩阵连乘 | $O(n^3)$ |
| LCS | $O(mn)$ |
| 0/1背包DP | $O(nM)$ |
| n皇后回溯 | 最坏 $\Omega(n!)$ 级别 |
| 图m着色 | 最坏 $O(m^n)$ 级别 |
2. 必背递推式
| 问题 | 递推 |
|---|---|
| 二分搜索 | $T(n)=T(n/2)+\Theta(1)$ |
| 归并排序 | $T(n)=2T(n/2)+\Theta(n)$ |
| 快排最好 | $T(n)=2T(n/2)+\Theta(n)$ |
| 快排最坏 | $T(n)=T(n-1)+\Theta(n)$ |
| Strassen | $T(n)=7T(n/2)+O(n^2)$ |
| 矩阵连乘 | $m[i][j]=\min{m[i][k]+m[k+1][j]+pip{k+1}p_{j+1}}$ |
| LCS | 匹配:$c[i][j]=c[i-1][j-1]+1$;不匹配:取上/左最大 |
| 0/1背包 | $f[i][j]=\max{f[i-1][j],f[i-1][j-w_i]+p_i}$ |
3. 最容易混淆的点
| 易混点 | 正确记忆 |
|---|---|
| 一般背包 vs 0/1背包 | 可分割用贪心;不可分割一般不用贪心 |
| 分治 vs DP | 分治子问题独立;DP子问题重叠 |
| 贪心 vs DP | 贪心当前选择;DP依赖子问题最优解 |
| 回溯 vs 分枝限界 | 回溯DFS;分枝限界BFS/优先队列 |
| BFS vs DFS | BFS队列;DFS栈/递归 |
| Prim vs Kruskal | Prim扩点;Kruskal选边 |
| 伸展树复杂度 | 单次最坏不一定 $O(\log n)$,分摊 $O(\log n)$ |
| Dijkstra适用条件 | 边权非负 |
| 关节点判定 | 根看孩子数,非根看 $Low[w]\ge d[u]$ |
考前速记口诀
1 | 大O上界,Ω下界,Θ夹中间; |
手写代码题高频模板 ⭐⭐⭐⭐⭐
这些模板按“考试手写”整理:短、好背、能覆盖核心逻辑。
真正答题时,代码后最好补一句:算法思想 + 时间复杂度 + 关键边界条件。
1. 二分搜索模板 ⭐⭐⭐⭐⭐
适用:有序数组查找。
1 | int BinarySearch(int a[], int n, int x) { |
必写说明:
1 | 每次比较后搜索区间减半,因此时间复杂度为 O(log n)。 |
易错点:
- 循环条件是
l <= r; - 更新时要写
mid + 1或mid - 1; mid = l + (r-l)/2防溢出。
2. 分治求最大最小元模板 ⭐⭐⭐⭐
适用:分治法基础代码题。
1 | void MaxMin(int a[], int l, int r, int &mx, int &mn) { |
必写说明:
1 | 左右分别求最大最小,再合并。 |
3. 归并排序模板 ⭐⭐⭐⭐⭐
适用:分治排序,稳定排序。
1 | void Merge(int a[], int l, int mid, int r) { |
必写说明:
1 | 递推式 T(n)=2T(n/2)+O(n),所以时间复杂度 O(nlogn)。 |
4. 快速排序模板 ⭐⭐⭐⭐⭐
适用:分治排序,常考 Partition。
1 | int Partition(int a[], int l, int r) { |
必写说明:
1 | Partition后,主元左边元素不大于主元,右边元素不小于主元。 |
易错点:
pivot要提前保存;- 最后必须
a[l] = pivot; - 递归区间要排除主元位置。
5. BFS模板 ⭐⭐⭐⭐⭐
适用:无权图最短路径、层次遍历。
1 |
|
必写说明:
1 | BFS使用队列,按层扩展。 |
6. DFS模板 ⭐⭐⭐⭐⭐
适用:图遍历、连通分量、时间戳、回溯类搜索。
1 |
|
带发现/完成时间版本
1 | int color[N]; // 0白,1灰,2黑 |
必写说明:
1 | DFS使用递归栈,邻接表时间复杂度 O(n+e)。 |
7. Dijkstra模板 ⭐⭐⭐⭐⭐
适用:非负权图单源最短路径。
1 | const int INF = 1000000000; |
必写说明:
1 | 每次从 V-S 中选 d 值最小的结点加入 S,再用它松弛其他结点。 |
8. Prim最小生成树模板 ⭐⭐⭐⭐
适用:无向连通带权图,稠密图常用。
1 | const int INF = 1000000000; |
必写说明:
1 | lowcost[v] 表示树外结点 v 到当前生成树的最小边权。 |
9. Kruskal最小生成树模板 ⭐⭐⭐⭐
适用:无向连通带权图,稀疏图常用。
1 |
|
必写说明:
1 | Kruskal按边权从小到大选边,用并查集判断是否成环。 |
10. 一般背包贪心模板 ⭐⭐⭐⭐
适用:物品可以分割的背包问题。
1 |
|
必写说明:
1 | 按单位重量收益 p/w 非增排序。 |
11. 最佳合并模式模板 ⭐⭐⭐⭐
适用:每次合并两个文件,总代价最小。
1 |
|
必写说明:
1 | 每次选两个最小文件合并,等价于哈夫曼树构造。 |
12. 矩阵连乘DP模板 ⭐⭐⭐⭐⭐
适用:求矩阵链最少数乘次数。
1 | const int INF = 1000000000; |
输出最优加括号:
1 | void Print(int i, int j) { |
必写说明:
1 | m[i][j] 表示 Ai...Aj 的最少数乘次数。 |
13. LCS最长公共子序列模板 ⭐⭐⭐⭐⭐
适用:两个序列的最长公共子序列。
1 | const int N = 100; |
输出一个LCS:
1 | void PrintLCS(string x, int i, int j) { |
必写说明:
1 | 若 xi == yj,则 c[i][j]=c[i-1][j-1]+1; |
14. 0/1背包DP模板 ⭐⭐⭐⭐⭐
适用:物品不可分割,每件只能选或不选。
1 | const int N = 100; |
必写说明:
1 | f[i][j] 表示前 i 件物品在容量 j 下的最大收益。 |
一维优化版
1 | int dp[M]; |
易错点:
1 | 一维0/1背包必须倒序枚举容量,否则会重复使用同一件物品。 |
15. n皇后回溯模板 ⭐⭐⭐⭐⭐
适用:回溯法、排列树、约束函数。
1 |
|
必写说明:
1 | x[k] 表示第 k 行皇后所在列。 |
16. 子集和数回溯模板 ⭐⭐⭐⭐
适用:子集树,选择/不选择当前元素。
1 | const int N = 100; |
带剩余和剪枝:
1 | void SubsetSum2(int k, int sum, int rest) { |
必写说明:
1 | 左分支选择当前元素,右分支不选择当前元素。 |
17. 图m着色回溯模板 ⭐⭐⭐⭐
适用:m叉树、图着色。
1 | const int N = 100; |
必写说明:
1 | 显式约束:color[k] ∈ {1,...,m}。 |
18. 0/1背包回溯模板 ⭐⭐⭐⭐
适用:回溯法求最优解。
1 | const int N = 100; |
必写说明:
1 | 左子树表示选择第 k 件物品,右子树表示不选择。 |
代码题考场速记模板 ⭐⭐⭐⭐⭐
分治法
1 | if (小规模) |
贪心法
1 | 按贪心准则排序; |
动态规划
1 | 初始化边界; |
回溯法
1 | void Backtrack(int k) { |
最可能手写的代码清单 ⭐⭐⭐⭐⭐
| 代码题 | 可能性 | 必背程度 |
|---|---|---|
| 二分搜索 | 高 | ⭐⭐⭐⭐⭐ |
| 归并排序 | 高 | ⭐⭐⭐⭐⭐ |
| 快速排序 Partition | 高 | ⭐⭐⭐⭐⭐ |
| BFS / DFS | 高 | ⭐⭐⭐⭐⭐ |
| Dijkstra | 高 | ⭐⭐⭐⭐⭐ |
| Prim / Kruskal | 中高 | ⭐⭐⭐⭐ |
| 矩阵连乘 | 中高 | ⭐⭐⭐⭐ |
| LCS | 高 | ⭐⭐⭐⭐⭐ |
| 0/1背包DP | 高 | ⭐⭐⭐⭐⭐ |
| n皇后回溯 | 高 | ⭐⭐⭐⭐⭐ |
| 子集和 / 图着色 | 中高 | ⭐⭐⭐⭐ |
| 一般背包贪心 | 中 | ⭐⭐⭐⭐ |
| 最佳合并模式 | 中 | ⭐⭐⭐⭐ |





