LOADING
2823 字
14 分钟
算法基础期末回顾
2026-01-29
统计加载中...

考试时间:2026 年 1 月 19 日 19:00-21:00,最后一门期末考试

最后一门期末考试!17 号考完 ICS 后开始同时复习离散数学和算法基础。先把 topic 给过了一遍。然后对着数据结构没有复习到的点逐一复习,首先把 P/NP 东西搞懂(比较感兴趣),然后逐一击破知识点。照例是扔给 notebooklm 来做的。算基有许多往年卷,看了两套。考前吃了麦当劳,在飞雪的夜晚边听歌边暴走着放松情绪。考场上判断和大题很快就看过去了。判断错 2 道,势能法不会,都是一些没看到没做过的知识点。后面算法设计题蒙出来一道多。

考完下着雪,地上已经有一层了。高新食堂窗户上写着 SBMLA。打车去吃烧鸟,提灯味道比较腥感觉,还是烤肉和寿喜锅好吃!

考试范围#

自己对照 AI 和老师的 PPT 梳理了一下,感觉算法基础这门课品味不错。使用高级的数据结构建模问题,从最基础的递归、动态规划、贪心、分治法出发,能创造出很多新奇的思路来解决问题,并可以用数学来证明结果的正确性

Topic:

  1. 算法复杂度分析:主定理、递归式求解、摊还分析
  2. 排序:比较排序(插入等 4 个,堆,快排)、线性时间排序(基数,计数,桶)、顺序统计量
  3. 高级数据结构:二叉排序树、红黑树、斐波那契堆、并查集;数据结构扩张
  4. 算法设计与分析:动态规划、贪心算法、摊还分析、分治法
  5. 图算法:最小生成树、单源最短路径、所有节点对最短路径、最大流
  6. 字符串算法:朴素算法、Rabin-Karp、有限自动机、KMP 算法
  7. 高级专题:NP 完全性,近似算法

如果您在准备考试,主定理的计算、网络流的 Ford-Fulkerson 手动模拟(画残存网络)、KMP 的 next 数组计算、以及 P/NP/NPC 的概念辨析是历年必考的重中之重。

Topic 1-4#

红黑树:根是黑的;叶子 (NIL) 是黑的;红节点的子节点必黑(不连续红);从任一节点到其后代叶子的所有路径包含相同数目的黑节点

Topic7#

P/NP 概念类:

  • P:多项式时间内能解决。欧拉回路
  • NP:多项式时间内能验证这个解是否正确
  • 约化:如果问题 A 可以在多项式时间内约化为问题 B,那么如果能解决 B,就一定能解决 A。B 的难度大于等于 A 的难度。记为 ApBA\leq_{p}B
    • 约化链:3-SAT(子语句是否为真)< 团问题 (无向图 G 中是否有完全子图 k) < 顶点覆盖(G 中是否有顶点子集,每个端点在子集中) < 哈密顿回路 < 旅行商问题 TSP
  • NP-Hard:如果所有 NP 问题能在多项式时间内归约到 NP-Hard 问题。NP-Hard 不一定属于 NP
  • NPC 问题:既是 NP 也是 NP-Hard。

近似算法

  • 近似比:解的代价 C 与最优解的代价 C*的比值
  • 常见近似算法:
    • 顶点覆盖:2-近似算法。任选一条边 (u, v),将 u, v 都加入覆盖集 C,图中删去 u, v 相关联的所有边,重复直到没有边。该 C 是最优解 C 的至多两倍
    • 旅行商问题,满足三角不等式:2-近似算法。找到最小生成树,先序遍历,按先序遍历,直接跳到新点。证明:MST 权重 \leq 最优 TSP 权重 \leq 2 MST 权重(一去一回)
    • 子集覆盖问题:从子集 S 选出最少的集合,能覆盖全集的元素 U。贪心算法:每次选择能覆盖剩余元素个数最多的子集。近似比 lnn\ln n
  • 近似方案:PO(多项式时间能找到最优解) ⊆ FPTAS (n 与误差倒数的多项式)⊆ PTAS(n 的误差倒数指数次方的多项式) ⊆ APX(常数近似) ⊆ log-APX ⊆ NPO

0/1 背包问题:伪多项式时间算法 O(nW)O(nW) W 为背包容量。多项式时间:运行时间是输入的比特位数 O(logW)O(\log W) 的多项式函数。伪多项式时间:是输入的数值的多项式函数

动态规划#

使用动态规划时,问题必须要拥有最优子结构、重叠子问题。DP 通过自顶向下或备忘录来避免重复运算。 关键:状态定义、状态转移方程。无后效性:算后面的时候,前面已经算好了 复杂度:多项式

  1. 线性模型:序列 DP,一维数组
  2. 网格模型:二维数组
  3. 区间 DP:矩阵乘法
  4. 背包问题:需要枚举背包容量

0-1 背包问题#

物体不可分时。物体可分:使用贪心

  1. 状态定义:dp[i][w]:在容量为 w 的情况下,在只能从前 i 个物品选择中,能选到的最大价值
  2. 状态转移:走到 i 物品时,若不拿 i 物品,则价值为 dp[i-1][w]。若拿走 i 物品,且背包装得下,则价值为 dp[i-1][w-w_i] + v_i。因此 dp[i][w]=max(dp[i1][w],dp[i1][wwi]+vi)dp[i][w]=max(dp[i-1][w],dp[i-1][w-w_i]+v_i)
  3. 算法执行流程:填表法。初始化 0,外层循环遍历 0 到 i,内层循环遍历 0 到 w。若 w < w_i,则装不下, dp[i][w] = dp[i-1][w]。否则用上面式子取最大值。

最大子数和: Kadane 算法#

求解数组中最大连续子数组的和的最大值

  1. 初始化:dp[i] 当前位置结束的位置和,maxSoFar 整个数组的和
  2. 状态转移: dp[i] = max(nums[i], dp[i] + nums[i]) :当前位置结束的和 = 前面数组与当前元素 + 从当前元素开始的和
  3. maxSoFar = max(maxSoFar, dp[i]):全局最大和 时间复杂度:O (n)
def maxSubArray(nums):
maxEndingHere = nums[0]
maxSoFar = nums[0]
for i in range(1, len(nums)):
maxEndingHere = max(nums[i], maxEndingHere + nums[i])
maxSoFar = max(maxSoFar, maxEndingHere)
return maxSoFar
# 示例
nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
print("最大子数组和为:", maxSubArray(nums)) # 输出: 6

LIS:最长递增子序列#

  • 想法最大递增序列 = max 以 i 结尾的最大递增序列。后者可以用 dp 表简单给出
  1. 状态定义:dp[i] 表示以 i 结尾的最长递增子序列长度
  2. 状态转移:对于 i,遍历 j = 0 到 i-1。若 a[i] > a[j],则 dp[i] = 1 + max(dp[j])
def lengthOfLIS(nums):
if not nums:
return 0
dp = [1] * len(nums)
for i in range(len(nums)):
for j in range(i):
if nums[i] > nums[j]: # 这个条件可以变掉
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)

时间复杂度优化#

  • 想法
    • 红黑树等数据结构优化:n => lgn
    • 最大化递增子序列长度 => 最小化递增子序列末尾项 。维护数组 tail[i] ,表示长度为 i 的所有递增子序列中,末尾最小的元素。Tail 数组严格自增,但是不一定是最后数组
  • 流程:for x in array:
    1. x > tail[end] :x 追加到 tail 末尾
    2. x <= tail[end]:使用二分查找,在 tail 中找到第一个大于等于 x 的数并用 x 替换
  • 时间复杂度:O(n lgn)
import bisect
def lengthOfLIS(nums):
tails = []
for num in nums:
idx = bisect.bisect_left(tails, num) # bisect_left 寻找第一个 >= num 的位置
if idx == len(tails): # 如果位置等于当前长度,说明 num 比 tails 所有元素都大
tails.append(num)
else: # 否则,替换掉该位置的元素,让该长度的子序列结尾更小
tails[idx] = num
return len(tails)

给定一个长度为 n 的序列 a1, a2, · · · , an,求长度为 k 的严格递增子序列的个数,要求时间复杂度 O (nk log n)。 解:dp[i][j] 表示以第 i 个数字结尾,长度为 j 的最大递增子序列。

dp[i][j]=k<idp[k][j1]dp[i][j]=\sum_{k<i} dp[k][j-1]

然后再用红黑树优化

LCS:最长公共子序列#

给定 X 和 Y,找出共同的最长子序列

  1. 状态定义:c[i,j] 表示 X 的前 i 个字符与 Y 的前 j 个字符最长公共子序列的长度
  2. 状态转移:
    1. x[i] == y [i] :两字符相等,c[i,j] = c[i-1,j-1] + 1
    2. 若不等于,说明不能作为公共结尾。抛弃 x 的末尾和 y 的末尾看看。c[i,j] = max(c[i-1,j], c[i,j-1] ) 取最大值。若某一项为 0 则为 0(边界条件)

矩阵链乘法#

区间 DP:大区间的解是小区间合并出来的。初始化区间=1,先枚举区间长度 len,再枚举起点 i,最后计算终点 j。在区间内枚举分割点 k<len,寻找最优位置

矩阵链乘法:

  1. 状态定义:m[i,j] 从 A_i 到 A_j 所需最少乘法数
  2. 状态转移:m[i,j]=minik<j(m[i,k]+m[k+1,j]+pipjpk)m[i,j]=min_{i\leq k<j}(m[i,k]+m[k+1,j]+p_{i}p_{j}p_{k}). 其中 m[i,i] = 0

最优搜索二叉树:有关键字 e1, e2… En 及其出现的概率 p1 p2 pn,构造二叉树使之查找效率最高。最优树的子树也是最优树,具有最优子结构

  1. 状态定义:e[i,j] 表示 i 到 j 的最优搜索代价
  2. 状态转移:e[i,j]=minik<j(e[i,k1]+e[k+1,i]+w(i,j))e[i,j]=min_{i\leq k<j}(e[i,k-1]+e[k+1,i]+w(i,j))w(i,j) 表示子树内所有概率的和。当选取根节点接入子树时,会使所有节点深度都加 1.

下面是习题课记的一些东西

状态转移和树与图#

一个公司有 n 个员工,每个员工(除了老板)都有一个直属上司。现在要举办一个晚宴,晚宴邀请到第 i 个员工会增加快乐值 vi,但是如果一个员工的直属上司来参加了,那么这个员工就无论如何也不肯来参加。问总的快乐值最大能够是多少。要求时间复杂度 O (n)。

上司舞伴问题:在图上的状态转移方程


最长链问题/最长链技术问题:求有向无环图中顶点数最多的路径长度和路径树? 解:先用拓朴排序,然后计算 f[u] :到达 u 最长链的长度

f[u]=max(u,v)E(f[v]+1)f[u]=\max_{(u,v)\in E}(f[v]+1)

方案数:再用 g[u] 数组旧仍以一个点到 u 的最长链的条数,状态转移方程:


给定一张 n 个点 m 条边的有向图(不一定是 DAG)。每个点有权值。求一条路径,使路径经过的点权值之和最大。允许多次经过一条边或者一个点,但是,重复经过的点,权值只计算一次。要求时间复杂度 O (n + m)。

强连通分量:如果经过强连通分量的一个点,则可以遍历强连通分量的任何点


贪心算法#

0/1 背包问题,要求设计一个近似比为 1/ 2 的贪心算法。要求时间复杂度为 O (n log n)。 每个物品有体积 viv_{i},价值 wiw_{i},按价值密度从大到小排序物品,

分治#

给定平面上 n 个点,要求输出距离最短的两个点,时间复杂度 O (n lg n)

  1. 预处理:按 x 坐标对所有点排序,得到数组 Px
  2. 分治:用中位 x 将平面分为两半。递归计算左半边最小值 δl\delta_{l} 和右半边 δr\delta_{r},最小值记为 δ\delta
  3. 跨越中线的最近点对:只考虑距离中线小于等于 δ\delta 的点,组成集合 S,将 S 按 y 轴排序。对于 S 中每个点,只和前后 k 个点进行比较。可证明 k 是常数
solve(r,l):
solve(r,mid)
solve(mid,l)

算法基础期末回顾
/posts/2026/01/af-review/
作者
Mirawind
发布于
2026-01-29
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Mirawind
Wish to be a firefly in the night...

统计加载中...
站点统计
文章数量
7
分类数量
3
标签数量
3
总字数
25,749
运行天数
46
最后更新
0 天前