GESP 7级
图的DFS/BFS
7级 · DP/图论/搜索
📖 一句话理解
前面学了DFS和BFS的基本思想,这里把它们应用到图上。图的DFS就是"从一个节点出发,沿着边尽量深地走,走不通就回溯";图的BFS就是"从一个节点出发,一层一层地扩展"。它们是图论算法的基础工具。
💡 为什么重要?
图的DFS/BFS能解决很多实际问题:判断两个节点是否连通、统计连通分量个数、求无权图最短路、拓扑排序等。它们是Dijkstra、Kruskal等高级算法的前置知识。GESP 7级必考。
📐 图的DFS
💻 图的DFS代码(逐行注释)
#include <iostream>
#include <vector>
using namespace std;
const int N = 105;
vector<pair<int,int>> adj[N]; // 邻接表存图
bool vis[N]; // vis[i] = true 表示节点i已访问过
int n; // 节点总数
// 从节点u开始DFS
void dfs(int u) {
vis[u] = true; // 标记当前节点已访问
// 遍历u的所有邻居
for (auto [v, w] : adj[u]) { // v是邻居节点,w是边权
if (!vis[v]) { // 如果邻居v还没被访问
dfs(v); // 递归访问v
}
}
}
// 调用:从某个起点开始DFS
// dfs(1); // 从节点1开始
// 统计连通分量个数
int countComponents() {
int cnt = 0;
for (int i = 1; i <= n; i++) {
if (!vis[i]) { // 如果节点i还没被访问
dfs(i); // 从i开始DFS,访问整个连通分量
cnt++; // 连通分量数+1
}
}
return cnt;
}📐 图的BFS(无权图最短路)
💻 图的BFS代码(逐行注释)
#include <iostream>
#include <vector>
#include <queue>
#include <cstring>
using namespace std;
const int N = 105;
vector<pair<int,int>> adj[N]; // 邻接表存图
int dist[N]; // dist[i] = 从起点到i的最短距离
int n;
void bfs(int start) {
memset(dist, -1, sizeof(dist)); // 初始化为-1,表示未访问
queue<int> q; // BFS用队列
q.push(start); // 起点入队
dist[start] = 0; // 起点距离为0
while (!q.empty()) { // 队列不为空就继续
int u = q.front(); // 取出队头
q.pop(); // 出队
// 遍历u的所有邻居
for (auto [v, w] : adj[u]) {
if (dist[v] == -1) { // 如果v还没被访问
dist[v] = dist[u] + 1; // 距离 = u的距离 + 1
q.push(v); // v入队
}
}
}
}📊 DFS vs BFS 在图上的应用
📝 各自擅长什么?
- DFS擅长:
- 判断连通性:从u能到达v吗?
- 统计连通分量
- 找所有路径
- 拓扑排序(配合入度)
- BFS擅长:
- 求最短路径(无权图)
- 按层遍历
- 找离起点最近的目标
🔢 例:判断二分图
给定一个图,判断能否将节点分成两组,使得每条边都连接不同组的节点。
💻 二分图判断代码
const int N = 105;
vector<int> adj[N];
int color[N]; // color[i] = 0未着色, 1红色, -1蓝色
bool dfs(int u, int c) {
color[u] = c; // 给当前节点着色
for (int v : adj[u]) {
if (color[v] == 0) {
// 邻居未着色,递归着相反颜色
if (!dfs(v, -c)) return false;
} else if (color[v] == c) {
// 邻居颜色相同 → 不是二分图
return false;
}
}
return true;
}
// 调用
// memset(color, 0, sizeof(color));
// bool isBipartite = dfs(1, 1);🚨 易错点
⚠️ 常见错误汇总
- 忘记标记visited:图可能有环,不标记visited会导致无限递归或死循环!
- BFS中visited在入队时标记:出队时才标记会导致同一节点多次入队。
- 连通分量要遍历所有节点:不能只从一个节点开始DFS就完事,要遍历所有未访问的节点。
- 有向图和无向图的区别:无向图的邻接表每条边要存两次(正向和反向)。
- BFS求最短路只适用于无权图:如果边有权重,需要用Dijkstra。
- dist数组初始化:用-1表示未访问,不能用0(因为起点距离也是0,会搞混)。
🎯 练习建议
📝 循序渐进练习路径
- 先练邻接表建图:确保能熟练用vector邻接表读入图。
- 做基础题目:
- P5318 查找文献(图的DFS/BFS遍历)
- P1331 海战(连通分量计数)
- P3366 最小生成树(后续学)
- 理解visited的必要性:在有环图上手动模拟不标记visited的后果。
- BFS求最短路练习:P1443 马的遍历(本质是图的BFS)。