一、图的遍历
§1.1 深度优先搜索(DFS)
找连通块、判断是否有环(如 207 题)等。部分题目做法不止一种。
模板(计算每个连通块的大小):
Python3
Java
C++
Go
def solve(n: int, edges: list[list[int]]) -> list[int]:
# 节点编号从 0 到 n-1
g = [[] for _ in range(n)]
for x, y in edges:
g[x].append(y)
g[y].append(x) # 无向图
vis = [False] * n
def dfs(x: int) -> int:
vis[x] = True # 避免重复访问节点
size = 1
for y in g[x]:
if not vis[y]:
size += dfs(y)
return size
# 计算每个连通块的大小
ans = []
for i, b in enumerate(vis):
if not b: # i 没有访问过
size = dfs(i)
ans.append(size)
return ans- 547. 省份数量
- 1971. 寻找图中是否存在路径
- 797. 所有可能的路径 1383
- 1306. 跳跃游戏 III 1397
- 841. 钥匙和房间 1412
- 2316. 统计无向图中无法互相到达点对数 1604
- 1319. 连通网络的操作次数 1633
- 2492. 两个城市间路径的最小分数 1680
- 3310. 移除可疑的方法 1711
- 2685. 统计完全连通分量的数量 1769
- 2192. 有向无环图中一个节点的所有祖先 1788
- 3387. 两天自由外汇交易后的最大货币数 1788
- 924. 尽量减少恶意软件的传播 1869 附思考题
- 2101. 引爆最多的炸弹 1880
- 721. 账户合并
- 207. 课程表 三色标记法判环
- 802. 找到最终的安全状态 1962
- 3695. 交换元素后的最大交替和 1984
- 928. 尽量减少恶意软件的传播 II 1985
- 2092. 找出知晓秘密的所有专家 2004
- 3108. 带权图里旅途的最小代价 2109
- LCP 07. 传递信息
- 261. 以图判树(会员题)
- 323. 无向图中连通分量的数目(会员题)
思维扩展:
- 1298. 你能从盒子里获得的最大糖果数 1825
§1.2 广度优先搜索(BFS)
求最短路等。要求边权都是 (或者说都是同一个正数)。
模板(单源最短路):
Python3
Java
C++
Go
# 计算从 start 到各个节点的最短路长度
# 如果节点不可达,则最短路长度为 -1
# 节点编号从 0 到 n-1,边权均为 1
def bfs(n: int, edges: list[list[int]], start: int) -> list[int]:
g = [[] for _ in range(n)]
for x, y in edges:
g[x].append(y)
g[y].append(x) # 无向图
dis = [-1] * n # -1 表示尚未访问到
dis[start] = 0
q = deque([start])
while q:
x = q.popleft()
for y in g[x]:
if dis[y] < 0:
dis[y] = dis[x] + 1
q.append(y)
return dis- 3243. 新增道路查询后的最短距离 I 1568
- 1311. 获取你好友已观看的视频 1653
- 3015. 按距离统计房屋对数目 I 1658
- 1129. 颜色交替的最短路径 1780
- 2039. 网络空闲的时刻 1865
- 2608. 图中的最短环 1904
- 815. 公交路线 1964
- 3807. 修复边以遍历图的最小成本(会员题)
§1.3 图论建模 + BFS 最短路
把状态抽象成图上的点,用 BFS 遍历这张图,计算从初始状态到目标状态的最短路长度。
可以锻炼状态设计能力。
- 322. 零钱兑换
- 279. 完全平方数
- 3877. 达到目标异或值的最少删除次数 1745
- 433. 最小基因变化
- 1284. 转化为全零矩阵的最少反转次数 1811
- 773. 滑动谜题 1815
- 752. 打开转盘锁 1878
- 3690. 拆分合并数组 1982
- 301. 删除无效的括号
- 514. 自由之路
- 847. 访问所有节点的最短路径 2201
- 854. 相似度为 K 的字符串 2377
- 127. 单词接龙 双向广搜
- 488. 祖玛游戏
- 3991. 使用前缀反转对数组进行排序(会员题)
- 3141. 最大汉明距离(会员题)
专题:跳跃游戏
- 2998. 使 X 和 Y 相等的最少操作次数 1795
- 1345. 跳跃游戏 IV 1810
- 2059. 转化数字的最小运算数 1850
- 1654. 到家的最少跳跃次数 2124
- 3629. 通过质数传送到达终点的最少跳跃次数 2139
- LCP 09. 最小跳跃次数
注:关于网格图的 DFS 和 BFS,请看 网格图题单。
二、拓扑排序

把拓扑排序想象成一个黑盒,给它一堆杂乱的先修课约束,它会给你一个井井有条的课程学习安排。
这一种在图上的「排序」,可以把杂乱的点排成一排。前提条件是图中无环,从而保证每条边都是从排在前面的点,指向排在后面的点。即对于任意有向边 , 一定在 之前。
§2.1 拓扑排序
模板:
Python3
Java
C++
Go
# 返回有向无环图(DAG)的其中一个拓扑序
# 如果图中有环,返回空列表
# 节点编号从 0 到 n-1
def topologicalSort(n: int, edges: list[list[int]]) -> list[int]:
g = [[] for _ in range(n)]
in_deg = [0] * n
for x, y in edges:
g[x].append(y)
in_deg[y] += 1 # 统计 y 的先修课数量
topo_order = []
q = deque(i for i, d in enumerate(in_deg) if d == 0) # 没有先修课,可以直接上
while q:
x = q.popleft()
topo_order.append(x)
for y in g[x]:
in_deg[y] -= 1 # 修完 x 后,y 的先修课数量减一
if in_deg[y] == 0: # y 的先修课全部上完
q.append(y) # 加入学习队列
if len(topo_order) < n: # 图中有环
return []
return topo_order学习拓扑排序前,请先完成 1557. 可以到达所有点的最少点数目,有助于理解拓扑排序。
- 210. 课程表 II
- 2115. 从给定原材料中找到所有可以做出的菜 1679
- 2392. 给定条件下构造矩阵 1961
- 802. 找到最终的安全状态 1962
- 1591. 奇怪的打印机 II 2291
- 1203. 项目管理 2419
- 1632. 矩阵转换后的排名 2530
- 2603. 收集树中金币 2712
- LCR 114. 火星词典
- 444. 序列重建(会员题)拓扑序是否唯一
- 3481. 应用替换(会员题)也可以记忆化搜索
- 269. 火星词典(会员题)
- 2371. 最小化网格中的最大值(会员题)同 1632 题
思维扩展:
§2.2 在拓扑序上 DP
一般是刷表法。
- 851. 喧闹和富有 1783
- 2050. 并行课程 III 2084
- 3620. 恢复网络路径
- 1857. 有向图中最大颜色值 2313
- 1136. 并行课程(会员题)
§2.3 基环树
- 2359. 找到离给定两个节点最近的节点 1715
- 2360. 图中的最长环 1897
- 684. 冗余连接 做法不止一种
- 685. 冗余连接 II
- 2876. 有向图访问计数 2210
- 2127. 参加会议的最多员工数 2449
- 2836. 在传球游戏中最大化函数值 2769 做法不止一种
- LCP 21. 追逐游戏
- 2204. 无向图中到环的距离(会员题)
思维扩展:
三、最短路
§3.1 单源最短路:Dijkstra 算法
模板:
Python3
Java
C++
Go
# 返回从起点 start 到每个点的最短路长度 dis,如果节点 x 不可达,则 dis[x] = math.inf
# 要求:没有负数边权
# 时间复杂度 O(n + mlogm),其中 m 是 edges 的长度。注意堆中有 O(m) 个元素
def shortestPathDijkstra(n: int, edges: list[list[int]], start: int) -> list[int]:
# 注:如果节点编号从 1 开始(而不是从 0 开始),可以把 n 加一
g = [[] for _ in range(n)] # 邻接表
for x, y, wt in edges:
g[x].append((y, wt))
# g[y].append((x, wt)) # 无向图加上这行
dis = [math.inf] * n
dis[start] = 0 # 起点到自己的距离是 0
h = [(0, start)] # 堆中保存 (起点到节点 x 的最短路长度,节点 x)
while h:
dis_x, x = heappop(h)
if dis_x > dis[x]: # x 之前出堆过
continue
for y, wt in g[x]:
new_dis_y = dis_x + wt
if new_dis_y < dis[y]:
dis[y] = new_dis_y # 更新 x 的邻居的最短路
# 懒更新堆:只插入数据,不更新堆中数据
# 相同节点可能有多个不同的 new_dis_y,除了最小的 new_dis_y,其余值都会触发上面的 continue
heappush(h, (new_dis_y, y))
return dis- 743. 网络延迟时间
- 3341. 到达最后一个房间的最少时间 I 1721 网格图
- 3112. 访问消失节点的最少时间 1757 理解原理
- 2642. 设计可以求最短路径的图类 1811
- 3604. 有向图中到达终点的最少时间 1845
- 1514. 概率最大的路径 1846
- 3650. 边反转的最小路径总成本 1854
- 3342. 到达最后一个房间的最少时间 II 1862 网格图
- 1631. 最小体力消耗路径 1948 网格图 做法不止一种
- 1786. 从第一个节点出发到最后一个节点的受限路径数 2079
- 3924. 有限重边的最小阈值路径 2080 也可以 0-1 BFS
- 3123. 最短路径中的边 2093
- 1976. 到达目的地的方案数 2095
- 778. 水位上升的泳池中游泳 2097 网格图 做法不止一种
- 2662. 前往目标的最小代价 2154
- 3377. 使两个整数相等的数位操作 2186
- 3928. 购买苹果的最低成本 II 2187
- 2045. 到达目的地的第二短时间 2202 也可以 BFS
- 3419. 图的最大边权的最小值 2243 做法不止一种
- 882. 细分图中的可到达节点 2328
- 2203. 得到要求路径的最小带权子图 2364
- 2577. 在网格图中访问一个格子的最少时间 2382 网格图
- 818. 赛车 2392
- 1928. 规定时间内到达终点的最小花费 2413
- 787. K 站中转内最便宜的航班 类似 1928 题
- 2699. 修改图中的边权 2874
- 1810. 隐藏网格下的最小消耗路径(会员题)
- 2093. 前往目标城市的最小费用(会员题)
- 2473. 购买苹果的最低成本(会员题)
- 2737. 找到最近的标记节点(会员题)
分层图最短路:
- 3970. 最多 K 个连续相同字符的最短路径 1840
- 3977. 有限电量到达目标节点的最少时间 2103
- LCP 35. 电动车游城市
- 3599. 划分数组得到最小 XOR 做法不止一种
- 3594. 所有人渡河所需的最短时间 2604
- 3778. 排除一个最大权重边的最小距离(会员题)
- 2714. 找到 K 次跨越的最短路径(会员题)
SPFA 与差分约束:
力扣上的 SPFA 题很少。
- 2589. 完成所有任务的最少时间 2381 做法不止一种
§3.2 全源最短路:Floyd 算法
Floyd 算法本质是三维 DP。理解 DP 一定要基于空间优化前的写法。
模板:
Python3
Java
C++
Go
# 返回一个二维列表,其中 (i,j) 这一项表示从 i 到 j 的最短路长度
# 如果无法从 i 到 j,则最短路长度为 math.inf
# 允许负数边权
# 如果计算完毕后,存在 i,使得从 i 到 i 的最短路长度小于 0,说明图中有负环
# 节点编号从 0 到 n-1
# 时间复杂度 O(n^3 + m),其中 m 是 edges 的长度
def shortestPathFloyd(self, n: int, edges: list[list[int]]) -> list[list[int]]:
f = [[math.inf] * n for _ in range(n)]
for i in range(n):
f[i][i] = 0
for x, y, wt in edges:
f[x][y] = min(f[x][y], wt) # 如果有重边,取边权最小值
f[y][x] = min(f[y][x], wt) # 无向图
for k in range(n):
for i in range(n):
if f[i][k] == math.inf: # 针对稀疏图的优化
continue
for j in range(n):
f[i][j] = min(f[i][j], f[i][k] + f[k][j])
return f- 2642. 设计可以求最短路径的图类 1811 动态加边
- 1334. 阈值距离内邻居最少的城市 1855
- 2976. 转换字符串的最小成本 I 1882
- 2959. 关闭分部的可行集合数目 2077
- 2977. 转换字符串的最小成本 II 2696
Bitset 优化 Floyd
四、最小生成树
涉及到 Kruskal 算法和 Prim 算法。前者一般用于稀疏图,后者一般用于稠密图。
注:如果要求最大生成树,把边权从大到小排序。
Kruskal 算法模板(用到了并查集,完整模板见 数据结构题单):
Python3
Java
C++
Go
class UnionFind:
def __init__(self, n: int):
# 一开始有 n 个集合 {0}, {1}, ..., {n-1}
# 集合 i 的代表元是自己
self._fa = list(range(n)) # 代表元
self.cc = n # 连通块个数
# 返回 x 所在集合的代表元
# 同时做路径压缩,也就是把 x 所在集合中的所有元素的 fa 都改成代表元
def find(self, x: int) -> int:
# 如果 fa[x] == x,则表示 x 是代表元
if self._fa[x] != x:
self._fa[x] = self.find(self._fa[x]) # fa 改成代表元
return self._fa[x]
# 把 from 所在集合合并到 to 所在集合中
# 返回是否合并成功
def merge(self, from_: int, to: int) -> bool:
x, y = self.find(from_), self.find(to)
if x == y: # from 和 to 在同一个集合,不做合并
return False
self._fa[x] = y # 合并集合。修改后就可以认为 from 和 to 在同一个集合了
self.cc -= 1 # 成功合并,连通块个数减一
return True
# 计算图的最小生成树的边权之和
# 如果图不连通,返回 math.inf
# 节点编号从 0 到 n-1
# 时间复杂度 O(n + mlogm),其中 m 是 edges 的长度
def mstKruskal(n: int, edges: list[list[int]]) -> int:
edges.sort(key=lambda e: e[2])
uf = UnionFind(n)
sum_wt = 0
for x, y, wt in edges:
if uf.merge(x, y):
sum_wt += wt
if uf.cc > 1: # 图不连通
return math.inf
return sum_wt- 1584. 连接所有点的最小费用 1858
- 3600. 升级后最大生成树稳定性 2301 做法不止一种
- 1489. 找到最小生成树里的关键边和伪关键边 2572
- 1135. 最低成本连通所有城市(会员题)
- 1168. 水资源分配优化(会员题)
思维扩展
五、欧拉路径/欧拉回路
涉及到 Hierholzer 算法。
- 332. 重新安排行程
- 753. 破解保险箱 2274
- 2097. 合法重新排列数对 2651
六、强连通分量/双连通分量
涉及到 Tarjan 算法。
- 1192. 查找集群内的关键连接 2085
- 1568. 使陆地分离的最少天数 2209
- LCP 54. 夺回据点
- 3383. 施法所需最低符文数量(会员题)
七、二分图染色
模板(交替染色法):
Python3
Java
C++
Go
# 返回图的二染色
# 如果是二分图,返回每个节点的颜色,用 1 和 2 表示两种颜色
# 如果不是二分图,返回空列表
# 时间复杂度 O(n+m),n 是点数,m 是边数
def colorBipartite(n: int, edges: list[list[int]]) -> list[int]:
# 建图(节点编号从 0 到 n-1)
g = [[] for _ in range(n)]
for x, y in edges:
g[x].append(y)
g[y].append(x)
# colors[i] = 0 表示未访问节点 i
# colors[i] = 1 表示节点 i 为红色
# colors[i] = 2 表示节点 i 为蓝色
colors = [0] * n
def dfs(x: int, c: int) -> bool:
colors[x] = c # 节点 x 染成颜色 c
for y in g[x]:
# 邻居 y 的颜色与 x 的相同,说明不是二分图,返回 False
# 或者继续递归,发现不是二分图,返回 False
if colors[y] == c or \
colors[y] == 0 and not dfs(y, 3 - c): # 1 和 2 交替染色
return False
return True
# 可能有多个连通块
for i, c in enumerate(colors):
if c == 0 and not dfs(i, 1):
# 从节点 i 开始递归,发现 i 所在连通块不是二分图
return []
return colors- 785. 判断二分图 1625
- 886. 可能的二分法 1795
- 3710. 最大划分因子 2135
关于二分图的最大匹配,见下面网络流的题目。其中标有「一对一」的题目也可以用带权二分图最大匹配做。
八、网络流
由于有其他做法(比如状压 DP),难度分仅供参考。
- 1947. 最大兼容性评分和 1704 一对一
- 3376. 破解锁的最少时间 I 1793 一对一
- 2850. 将石头分散到网格图的最少移动次数 2001 一对多
- 1879. 两个数组最小的异或值之和 2145 一对一
- 1349. 参加考试的最大学生数 2386 二分图最大独立集
- 2172. 数组的最大与和 2392 多对一
- 3276. 选择矩阵中单元格的最大得分 2403
- 1595. 连通两组点的最小成本 2538 带权二分图最小边覆盖
- 3257. 放三个车的价值之和最大 II 2553
- LCP 04. 覆盖 二分图最大匹配·模板题
- LCP 38. 守卫城堡 最小割
- 1820. 最多邀请的个数(会员题)二分图最大匹配·模板题
- 2403. 杀死所有怪物的最短时间(会员题)同 3376 题
- 3385. 破解锁的最少时间 II(会员题)同 3376 题
- 1066. 校园自行车分配 II(会员题)一对一,但不是完美匹配
- 4004. 使循环数组余额非负的最少移动次数 II(会员题)
- 2123. 使矩阵中的 1 互不相邻的最小操作数(会员题)二分图最大独立集
模拟费用流
九、其他
- 1042. 不邻接植花 1712
- 1761. 一个图中连通三元组的最小度数 2005
- 2508. 添加边使所有节点度数都为偶数 2060
- 1579. 保证图可完全遍历 2132
- 2065. 最大化一张图中的路径价值 2178
- 1697. 检查边长度限制的路径是否存在 2300
- 2242. 节点序列的最大得分 2304
- 2493. 将节点分成尽可能多的组 2415 推荐
- 1782. 统计点对的数目 2457
- 3666. 使二进制字符串全为 1 的最少操作次数 2477 进阶 BFS / Gale-Ryser 定理
- 2612. 最少翻转操作数 2824 进阶 BFS
- 3435. 最短公共超序列的字母出现频率 3028
- 466. 统计重复个数 做到
- LCP 16. 游乐园的游览计划
- 277. 搜寻名人(会员题)
- 1724. 检查边长度限制的路径是否存在 II(会员题)
- 2077. 殊途同归(会员题)三元环计数
- 3656. 判断是否存在简单图(会员题)Erdős–Gallai 定理
十、树上算法
见 链表、树、回溯 题单的第三章节。
关联题单
- 关于网格图的 DFS 和 BFS,见 网格图题单。
算法题单
- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
- 单调栈(基础/矩形面积/贡献法/最小字典序)
- 网格图(DFS/BFS/综合应用)
- 位运算(基础/性质/拆位/试填/恒等式/思维)
- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
- 链表、树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA)
- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
如果你发现有题目可以补充进来,欢迎评论反馈。
评论 (136)


