☰
算法设计与分析期末复习:必背伪代码模板与复杂度总结
2026/10/7 1:36:01 网站建设 项目流程

期末复习周最让人头疼的,往往不是题目本身,而是整本书里二三十个经典算法,全都似曾相识,真到考场上让你手写伪代码,脑子却一片空白。算法设计与分析这门课,考试核心其实就一句话:给你一个经典问题,让你写出求解过程的伪代码,再算一算时间复杂度和空间复杂度。无论是武汉理工还是其他高校,期末题型的套路基本都围绕“分治、动态规划、贪心、回溯、分支限界、图算法”这几个大模块展开,而伪代码就是所有模块的通用语言。

这篇文章我按课程重点顺序,把最常考的算法全部过一遍,每个算法都给出可以直接背、可以直接用的伪代码模板,并标注了容易丢分的细节和复杂度推导思路。不管是平时上课听得云里雾里,还是考前三天才打算突击,这篇笔记都能当你的“急救包”。建议把每个伪代码都自己动手抄一遍、跑一遍边界用例,因为你以为你看懂了,和你能在手不抖的情况下写对,中间隔着无数次手抖。

1. 整体复习思路与考点分布

很多同学复习算法设计与分析,容易陷入一个误区:把大量时间花在看书和看PPT上,觉得“看懂了”就等于“会了”。期末考里的伪代码大题恰恰不会给你编程环境,甚至不要求你跑通真实代码,老师看的只是你的逻辑是否顺畅、边界条件是否完备、复杂度分析是否正确。所以这门的复习主旋律应该是:手写、手推、手算。

1.1 为什么期末必考伪代码

伪代码介于自然语言和真实代码之间,不需要关注分号、括号、头文件这种语法细节,却又要求你把算法的顺序、分支、循环、递归结构全部表达清楚。老师能在半页纸内把代码逻辑看完,也特别方便挖坑来考:比如二分查找的区间边界,快速排序的partition终止条件,动态规划数组是从0开始填充还是从1开始填充。这些细节用真实编程语言写容易因为工具链问题出bug,但用伪代码考察的是纯粹的算法思维,反而更见基本功。

1.2 常见题型与复习优先级

我翻了最近几年各大高校的期末试卷,包括平时作业和往年考题,题目风格大致可以归纳成下面几类,复习优先级也一并列出来:

题型考察内容优先级
选择题/填空题时间复杂度阶、算法思想匹配、各类算法适用条件高,最好拿分
手写伪代码分治、DP、贪心、回溯、分支限界经典算法最高,大题基本在这里
复杂度推导递归树、主方法、递推方程展开高,会背公式也要会算
手算过程题填动态规划表、构造哈夫曼树、找关键路径高,需要熟练手动模拟
算法应用题用学过的方法解决一个“新问题”中,套路为先

从优先级就能看出来,伪代码背诵和手算能力是绝对主体。而这两项能力没有捷径,只能靠“重复拆解+默写”。

1.3 一条可以照做的复习路线

如果你时间紧,我建议按照“分治一横、DP一纵”的思路复习:先花一天把分治类的二分、归并、快排、最接近点对摸熟,因为它们的核心是“递归结构+合并操作”,是后面很多算法的基础;再用两天把动态规划几个经典题目彻底吃透,因为DP是期末大题的重灾区;之后花一天整理贪心经典场景,再花一天熟悉回溯和分支限界的代码模板;最后留出时间刷图算法和并查集,顺带把复杂度的证明套路背下来。下面每一章,我都会给出可以直接“抄作业”的伪代码。

2. 分治算法必背模板

分治法的思想再简单不过:把一个规模为n的问题拆成若干个规模更小的子问题,分别求解后再合并。但考场上真正写分治伪代码时,很多人会漏掉“递归出口”和“合并”这一步。记住一个框架:

Algorithm DivideAndConquer(P) if |P| <= n0 return Conquer(P) // 直接求解小规模问题 divide P into P1, P2, ..., Pk // 分解 for i = 1 to k yi = DivideAndConquer(Pi) // 递归求解 return Merge(y1, y2, ..., yk) // 合并

2.1 二分搜索:边界条件是最大扣分点

二分搜索虽然简单,但每次考试都能筛掉一批人,问题基本都出在区间边界上。这里我给一个左闭右闭区间版本,避免混淆:

Algorithm BinarySearch(A[0..n-1], x) // 输入:有序数组A,目标值x // 输出:x的下标,若不存在返回-1 l = 0, r = n - 1 while l <= r mid = floor((l + r) / 2) if A[mid] == x return mid else if A[mid] > x r = mid - 1 else l = mid + 1 return -1

为什么退出条件是l <= r而不是l < r?因为当区间内只剩一个元素时,l == r,这个元素仍然需要再检查一次。如果把等号漏掉,就会漏判边界值。同里,r = mid - 1和l = mid + 1一定要记得加减1,否则可能陷入死循环。

复杂度推导也很固定:每次规模减半,递推方程T(n) = T(n/2) + O(1),由主方法得O(log n)。空间复杂度如果是递归实现是O(log n)的栈深,迭代实现是O(1)。

2.2 归并排序:合并过程就是看细节的

归并排序考的不仅是排序本身,还经常顺带考“求逆序对”。它的框架是递归地把数组分成两半,分别排好序再合并。核心伪代码如下:

Algorithm MergeSort(A, l, r) if l >= r return mid = floor((l + r) / 2) MergeSort(A, l, mid) MergeSort(A, mid + 1, r) Merge(A, l, mid, r) Algorithm Merge(A, l, mid, r) // 将A[l..mid]与A[mid+1..r]合并为有序序列,存到临时数组tmp中 i = l, j = mid + 1, k = 0 while i <= mid and j <= r if A[i] <= A[j] tmp[k++] = A[i++] else tmp[k++] = A[j++] while i <= mid tmp[k++] = A[i++] while j <= r tmp[k++] = A[j++] for p = 0 to k - 1 A[l + p] = tmp[p]

归并排序的时间复杂度是O(n log n),这一点大家都记得,可递推方程要会写:T(n) = 2T(n/2) + O(n),用主方法或递归树展开,最后一层总代价是O(n),共有log n层,所以总计O(n log n)。

如果题目考“求逆序对”,只需要在 Merge 里加一行:当A[i] > A[j]时,说明左半边剩余的所有元素(从 i 到 mid)都和当前A[j]构成逆序对,答案累加mid - i + 1。这是最常考的一个变体,一定要理解这个计数原理。

2.3 快速排序:partition是灵魂

快速排序考的频率不比归并排序低,尤其喜欢考“一趟排序后的序列状态”或者“Partition 函数的写全”。我推荐最不容易写错的 Lomuto 划分版本:

Algorithm QuickSort(A, l, r) if l >= r return p = Partition(A, l, r) QuickSort(A, l, p - 1) QuickSort(A, p + 1, r) Algorithm Partition(A, l, r) // 选择A[r]作为主元 pivot = A[r] i = l - 1 for j = l to r - 1 if A[j] <= pivot i = i + 1 swap(A[i], A[j]) swap(A[i + 1], A[r]) return i + 1

Lomuto 划分的思路是维护一个“小于等于主元”的区间,i是区间最后一个位置,j是扫描指针,扫描到比主元小的元素就把它换到前面。这个写法比 Hoare 的双向扫描更难写错,期末答题优先用它。

注意:快速排序平均情况是O(n log n),最坏情况O(n^2)(比如数组已经有序且每次都取最后一个元素为主元)。很多同学只写“平均O(n log n)”而忘记最坏情况,考试是要扣分的。快速排序不稳定,但归并排序稳定,这个也常出填空题。

2.4 大整数乘法与矩阵乘法:能写思路就行

大整数乘法和 Strassen 矩阵乘法在有些学校是选讲内容,但期末选择题偶尔会问它们的复杂度。Karatsuba 算法把两个n位大数相乘的复杂度从O(n^2)降到O(n^(log2 3)),约等于O(n^1.585);Strassen 矩阵乘法把普通矩阵乘法的O(n^3)降到O(n^(log2 7)),约等于O(n^2.807)。如果考伪代码,通常只需要写出“把矩阵分块成四个子矩阵,用7次乘法和若干次加法递归计算”这个结构,不要求把7个式子全默写出来,但最好能说出“相比于直接分块8次乘法,减少了一次矩阵乘法”这句话。

3. 动态规划必背模板

动态规划是算法期末的重头戏,分值大、题型多,也是最容易拉开差距的部分。很多人觉得动态规划难,是因为没有形成固定套路。其实期末考的DP题基本都是经典模板题,解题顺序完全可以固定下来:定义状态、写转移方程、确定初始条件、确定遍历顺序,最后手算验证一个例子。

3.1 0-1背包:二维DP和一维优化都要会

0-1背包问题描述:有n个物品,每个物品重量w[i]、价值v[i],背包容量W,每件物品只能选一次,求能装下的最大价值。

定义dp[i][j]表示“前i个物品中,选出总重量不超过j的物品,能获得的最大价值”。状态转移:

if j < w[i] dp[i][j] = dp[i-1][j] else dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i])

这里i从1到n,j从0到W。初始化dp[0][j] = 0。最终答案就是dp[n][W]。

需要格外注意的是:在考场中,如果你写的是二维DP,物品下标建议从1开始,这样转移方程里dp[i-1]不会出现负下标。但数组本身在纸上写的时候,下标从0还是从1要和你的伪代码保持一致,不要一会儿从0一会儿从1。

一维优化的写法也很常考。因为dp[i][j]只依赖于dp[i-1][…],我们可以把第一维压掉,但要小心:内层遍历容量时必须倒序。

for i = 1 to n for j = W downto w[i] dp[j] = max(dp[j], dp[j - w[i]] + v[i])

为什么j一定要从大到小?因为正序的话,dp[j - w[i]]可能在当前这一轮已经被更新过了,也就是同一个物品被重复选了第二次,这就变成了完全背包。这个知识点几乎是必考,必须理解清楚。

3.2 最长公共子序列(LCS):要会填表,还要会回溯

LCS 问题定义:给定两个字符串X[1..m]、Y[1..n],求它们最长公共子序列的长度。

设dp[i][j]表示X[1..i]和Y[1..j]的 LCS 长度。转移方程:

if X[i] == Y[j] dp[i][j] = dp[i-1][j-1] + 1 else dp[i][j] = max(dp[i-1][j], dp[i][j-1])

初始化dp[0][j] = 0,dp[i][0] = 0。时间复杂度O(m*n),空间复杂度O(m*n)。

期末考试喜欢考“画出DP表并写出LCS”。光会用递推公式不够,还要会回溯:从dp[m][n]出发,如果X[i] == Y[j],说明这个字符在LCS中,向左上移动并记录;否则比较dp[i-1][j]和dp[i][j-1],向较大的那个方向移动。如果相等,任选一个方向即可,因为这时候两个方向的LCS长度相等,但具体的LCS序列可能不同。

3.3 矩阵链乘法:区间DP的典范

矩阵链乘法(Matrix Chain Multiplication)给定一系列矩阵A1, A2, ..., An,矩阵Ai的规模是p[i-1] × p[i],要求完全加括号,使标量乘法次数最少。

定义dp[i][j]为“计算Ai…Aj所需的最小乘法次数”,转移方程:

dp[i][j] = min{ dp[i][k] + dp[k+1][j] + p[i-1] * p[k] * p[j] } 其中 i <= k < j

初始化dp[i][i] = 0,而对角线之外的dp[i][j]初始化为无穷大。遍历顺序非常关键:先枚举区间长度 len,再枚举左端点 i,然后枚举分割点 k。

for len = 2 to n for i = 1 to n - len + 1 j = i + len - 1 dp[i][j] = INF for k = i to j - 1 dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] + p[i-1] * p[k] * p[j])

很多新手第一次写会直接for i = 1 to n、for j = i+1 to n,然后发现dp[k+1][j]还没算出来。原因就是没有按区间的长度从短到长递推。期末如果考这个,答题时一定要把“最外层循环是区间长度”写在显眼位置,老师一眼就能看出你理解了区间DP的本质。

3.4 编辑距离:三种操作一个都不能少

编辑距离(Edit Distance)将字符串A变成B,允许插入、删除、替换一个字符,求最少操作次数。

定义dp[i][j]表示A[1..i]变成B[1..j]的最少操作次数,初始化dp[i][0] = i,dp[0][j] = j。转移:

if A[i] == B[j] dp[i][j] = dp[i-1][j-1] else dp[i][j] = 1 + min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1])

其中dp[i-1][j-1]对应替换,dp[i-1][j]对应删除A[i],dp[i][j-1]对应在A中插入B[j]。编辑距离时间复杂度O(m*n),空间也能优化到O(min(m,n)),期末如果只让写伪代码,写二维版本即可,但最好在复杂度分析时提一句“可以用滚动数组优化空间”。

4. 贪心算法必背模板

贪心算法和动态规划最大的区别是:贪心每一步都做出当前看起来最优的选择,并且之后不回头。它不是对所有问题都有效,期末考贪心题时,除了写算法,还经常要求“证明贪心选择的正确性”。很多学生只背代码,不会证明,这里我把常用的证明套路一并写出来。

4.1 活动选择问题:按结束时间排序是标准解

活动选择问题是贪心最经典的入门题。有n个活动,每个活动有开始时间s[i]和结束时间f[i],同一时间只能做一个活动,求能参与的最多活动数。

贪心策略:按结束时间从小到大排序,每次都选“结束时间最早的且与已选活动不冲突”的活动。

Algorithm ActivitySelect(s[1..n], f[1..n]) // 假设活动已按f[i]升序排序 A = {1} j = 1 for i = 2 to n if s[i] >= f[j] A = A ∪ {i} j = i return A

正确性证明用“交换论证”:假设最优解中第一个活动是 k,而贪心选择的是结束时间最早的1,因为f[1] <= f[k],所以把 k 换成1不会影响后续活动选择,也不会减少活动数量。以此类推,不断替换,贪心解不差于最优解。这个证明套路在期末考试里可以直接用。

4.2 哈夫曼编码:优先队列加贪心合并

哈夫曼编码常用于构造最优前缀码。给定n个字符出现的频率,要求构造一棵带权路径长度WPL最小的二叉树。

贪心策略很明确:每次从优先队列中取出两个权值最小的节点,合并成一个新节点,新节点权值为两者之和,再放回队列,直到只剩一个节点。

Algorithm Huffman(C) Q = C // 每个字符作为单节点树,按权值入优先队列 for i = 1 to n - 1 z = new Node z.left = x = ExtractMin(Q) z.right = y = ExtractMin(Q) z.weight = x.weight + y.weight Insert(Q, z) return ExtractMin(Q)

如果期末让你“给定一组权值,构造哈夫曼树”,一定要手动模拟这个过程,不能只写代码。WPL的计算方式是:其值等于所有叶子节点的权值乘以它们所在的深度之和,也可以理解为每次合并时把两个子节点的权值累加到总代价中,即WPL += x.weight + y.weight。这两种方法算出来的结果一样,后者在模拟时更好用,不容易漏加。

4.3 单源最短路径:Dijkstra是必考算法

Dijkstra算法适用于边权非负的图,求解从源点s到所有其他顶点的最短路径长度。

Algorithm Dijkstra(G, w, s) for each vertex v in V dist[v] = INF visited[v] = false dist[s] = 0 使用优先队列Q,初始插入(s, 0) while Q is not empty u = ExtractMin(Q) if visited[u] continue visited[u] = true for each edge (u, v) in E if dist[v] > dist[u] + w(u, v) dist[v] = dist[u] + w(u, v) 将(v, dist[v])插入Q return dist

期末考试经常考手动执行过程:从源点出发,每次从未访问顶点中选出距离最小的点,松弛它出边连接的顶点。这个手算流程一定要练熟。复杂度方面,用普通数组实现是O(V^2),用二叉堆/优先队列实现是O((V+E) log V)。优先队列实现时要注意:一个顶点可能被重复插入队列多次,所以取出时要判断是否已经访问过。

4.4 最小生成树:Prim和Kruskal都要会

最小生成树两种经典算法:

Prim算法适合稠密图,从任意一个顶点开始,不断把“连接已选顶点集合和未选顶点集合的最小权值边”加入树中,代码结构和Dijkstra很像,区别是更新的数组从“到源点距离”变成了“到当前已选集合的距离”。

Kruskal算法适合稀疏图,把所有边按权值从小到大排序,然后用并查集判断是否会形成环,不会形成环就加入。

Algorithm Kruskal(G, w) A = {} 将所有边按权值从小到大排序 for each edge (u, v) in sorted edges if Find(u) != Find(v) A = A ∪ {(u, v)} Union(u, v) return A

如果考手算,Kruskal只需要“从小到大选边,不成环就选”这一句话就能模拟;Prim 则需要每次都检查当前已选点集的所有邻边,选取最小的一条并保证不产生环。两者复杂度要记清楚:Prim 用邻接矩阵是O(V^2);Kruskal 主要开销在排序,复杂度O(E log E)。

5. 回溯与分支限界必背模板

回溯和分支限界都是系统搜索解空间的方法。回溯对应深度优先搜索,分支限界对应广度优先搜索或优先队列式搜索。期末对回溯的代码要求更高,因为它的解空间树结构(子集树、排列树)经常单独出题。

5.1 回溯算法的通用框架

回溯法最通用的伪代码如下:

Algorithm Backtrack(t) if t > n output(x) // 找到一个解 else for i = 1 to 可选值个数 x[t] = 选择i if Constraint(t) and Bound(t) Backtrack(t + 1)

这里的Constraint(t)是约束函数,Bound(t)是限界函数。期末喜欢考“画出解空间树”“判断某个剪枝是否合理”。如果你对一棵子集树调用回溯,复杂度是O(2^n);排列树是O(n!)。这两个阶别必须能脱口而出。

5.2 n皇后问题:回溯的典型代表

在 n×n 棋盘上放置n个皇后,使它们互不攻击。用一维数组x[1..n]表示第i行皇后所在的列号。当在第 t 行第 i 列放置皇后时,约束条件是:不同的皇后不能在同一列,x[k] != x[t];不能在同一对角线,|x[k] - x[t]| != |k - t|。

Algorithm NQueens(t) if t > n count = count + 1 // 找到一个解 else for i = 1 to n x[t] = i if Place(t) NQueens(t + 1) Algorithm Place(t) for k = 1 to t - 1 if x[k] == x[t] or abs(x[k] - x[t]) == abs(k - t) return false return true

n皇后问题的解空间树是排列树的变种,因为每一行只能放一个皇后,每一列也只能被一个皇后占用。手算时常用的技巧是“对角线判断用行差和列差是否相等”,这一点在考试中写abs函数即可,不需要过度展开。

5.3 装载问题与0-1背包回溯:限界函数的威力

0-1背包用回溯法时,除了约束cw + w[i] <= W(当前重量加新物品重量不能超过容量)之外,更重要是限界函数。如果当前已装价值cp加上剩余所有物品的总价值rp都无法超过当前最优值bestp,就可以直接剪枝。

伪代码可以这样写:

Algorithm BacktrackKnap(i) if i > n bestp = max(bestp, cp) return if cw + w[i] <= W cw = cw + w[i] cp = cp + v[i] BacktrackKnap(i + 1) cw = cw - w[i] cp = cp - v[i] if cp + rp - v[i] > bestp BacktrackKnap(i + 1) rp = 剩余物品的总价值(初始给定)

很多同学会漏掉最后一步“不选当前物品”时的限界条件。如果不加这个剪枝,回溯就会退化成暴力枚举所有子集,复杂度仍然是O(2^n);加上限界函数之后,实际搜索规模会大幅缩小。注意:限界条件写成cp + rp > bestp是一种更保守但仍正确的写法,也可以写成cp + rp - v[i] > bestp,含义是“即使不选i,剩余物品全选上仍有可能超过当前最优,就继续搜”,这两种写法考试中都能接受,但最后一种剪得更狠。

如果要求写出0-1背包的“子集树”结构,每一层代表对一个物品决定“选”或“不选”,左子树表示选,右子树表示不选,叶子节点数量是2^n。

5.4 分支限界法:FIFO式与优先队列式的区别

分支限界法用广度优先或“最小耗费优先”方式搜索解空间树。0-1背包的分支限界法常用优先队列(按当前已装价值或上界排序),每次优先扩展上界最大的节点,找到的第一个可行解往往就是最优解。 FIFO队列版则是一层层扩展,逻辑简单但搜索空间更大。

考试中分支限界不一定要求写完整代码,但至少要能够说明:队列式分支限界是用队列管理活结点,优先队列式是用优先队列管理活结点;分支限界和回溯的区别在于回溯是DFS深度优先、一次只保留一条路径;分支限界是BFS/优先队列式、需要维护一个活结点表。

6. 图算法与并查集速记

图算法这部分内容看着多,但期末常考的核心其实就几个:DFS、BFS的伪代码、拓扑排序、并查集。关键路径在部分学校的考纲里会出现,但相对低频。

6.1 DFS与BFS:两种遍历必须背熟

DFS可以用递归写,也可以用显式栈,考试优先写递归版本最省事:

Algorithm DFS(G, v) visited[v] = true for each neighbor u of v if not visited[u] DFS(G, u)

BFS用队列:

Algorithm BFS(G, s) for all v in V, visited[v] = false visited[s] = true Q = {s} while Q is not empty u = Dequeue(Q) for each neighbor v of u if not visited[v] visited[v] = true Enqueue(Q, v)

DFS 常用来判断连通分量数量、检测环;BFS 可以用来求无权图的最短路径。两种遍历的时间复杂度都写O(V + E)或者O(n + m),注意写成O(n*m)会直接丢分。

6.2 拓扑排序:Kahn算法

拓扑排序针对有向无环图。Kahn 算法的核心是反复找入度为0的顶点:

Algorithm TopologicalSort(G) for each vertex v in G compute indegree[v] 将入度为0的顶点入队Q count = 0 while Q is not empty u = Dequeue(Q) 输出u count = count + 1 for each neighbor v of u indegree[v] = indegree[v] - 1 if indegree[v] == 0 Enqueue(Q, v) if count != V 说明图中有环

如果期末问“能否用DFS求拓扑排序”,答案是可以:DFS搜索完成顺序的反序就是拓扑序。但Kahn算法更好写,也更好证明正确性,优先背这个。

6.3 并查集:路径压缩和按秩合并

Kruskal算法以及很多图相关的题目都依赖并查集。并查集两个核心操作:找根和合并,代码很短:

Algorithm Find(x) if parent[x] != x parent[x] = Find(parent[x]) // 路径压缩 return parent[x] Algorithm Union(x, y) rootX = Find(x) rootY = Find(y) if rootX == rootY return if rank[rootX] < rank[rootY] parent[rootX] = rootY else if rank[rootX] > rank[rootY] parent[rootY] = rootX else parent[rootY] = rootX rank[rootX] = rank[rootX] + 1

期末考试如果让手写并查集,通常不会卡在“按秩合并”这一步,但路径压缩不写会扣分。复杂度方面,同时使用路径压缩和按秩合并时,单次操作的均摊时间复杂度近似O(alpha(n)),其中alpha是阿克曼函数的反函数,增长极慢,可以当作常数阶。

7. 考场高频陷阱与背题技巧

最后这部分,我不再列新算法,而是把前面所有内容里最容易在考场上“莫名丢分”的点梳理一遍。这些不是理论问题,全是实践经验。

7.1 三个最容易写错的地方

第一个是动态规划的初始化。0-1背包、LCS、编辑距离都有独立的初始化边界,很多同学在写伪代码时直接写双重循环,忘了dp[i][0] = 0或dp[0][j] = 0。应该记住:动规题先写初始化、再写转移,顺序不要乱。

第二个是回溯里的恢复现场。选了当前物品递归下去之后,要把cw和cp减回来,把visited标回false,否则下一次分支会带着错误状态继续搜索。考试时哪怕整体思路对,恢复现场漏写,代码就不完整,分数会大打折扣。

第三个是复杂度分析写错。比如归并排序递推方程写错成T(n) = 2T(n/2) + n^2这种;二分搜索写成O(n^2);Dijkstra 用堆优化却写成O(V^2)。建议考前把每个高频算法的递推方程和最终复杂度列成一张表,反复看几遍。

7.2 手算题怎么练才高效

遇到“填DP表”或者“构造哈夫曼树”这类手算题,技巧是“慢一点,但别跳步”。我复习时会找一张A4纸,把0-1背包问题画成一个行数为物品数+1、列数为容量+1的表格,一格一格填。填到一半如果发现某个位置不知道值怎么来,就说明转移方程还没吃透,这时回头看伪代码,比闷头背十遍都有效。

哈夫曼树的手算也类似:每合并一次,就把两个最小的权值圈出来,再画一个新节点,直到所有节点合并成一棵树。最后算WPL时,建议在纸上把每个叶子节点的权值和深度写在旁边,防止漏算。

7.3 关于“拓展考点”的一点提醒

有些高校的期末试卷会出现看似超纲的内容,比如 MapReduce 相关的伪代码、以及一些算法设计理念的应用题。这类题目本质上是把已经学过的思想搬到一个新场景里,比如 Map 阶段对应并行拆分,Reduce 阶段对应结果合并,本质上还是分治的思路。遇到不会的题不要慌,往“分治、贪心、动态规划、回溯”几个方向上去套。如果把 MapReduce 类的题出成了编程题,那就需要你在真实代码里实现一个 mapper 和 reducer,这已经不是纯伪代码能解决的了,需要额外练一练实际编程。

7.4 考前两天的背诵清单

这里是我个人比较推荐的一个“应急清单”,把它当作最后冲刺的目录就行:

模块必背内容自查标志
分治二分搜索、归并排序、快速排序能默写边界条件,能写出递推方程
DP0-1背包、LCS、矩阵链乘、编辑距离能填表能回溯,能说清遍历顺序
贪心活动选择、哈夫曼、Dijkstra、Prim/Kruskal能用交换论证证明活动选择
回溯/分支限界n皇后、0-1背包回溯、通用框架能画出子集树,能写剪枝函数
图算法DFS/BFS、Kahn拓扑排序、并查集能说出复杂度,能处理环的判断

最后再分享一个我自己的小习惯:每次背完一个算法的伪代码,我会在本子上手写一遍,然后用一个非常小的样例去“人肉跑”一遍,比如二分查找就在纸上写[1,3,5,7],找5,一步一步把l、r、mid的变化全写出来。这个过程会暴露大量你以为自己知道但其实不知道的细节。算法设计与分析这门课,向来不是光靠看就能过的,拿起笔,把每个伪代码“写”进肌肉记忆,考场上那些看似突然冒出来的大题,其实都只是你练过无数遍的旧相识。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询