- gf25055 的博客
《死亡笔记-代码模板》BFS答题模板
- @ 2026-3-19 14:40:18
一、连通块问题 / 洪水填充(Flood Fill) 典型场景:统计二维网格中连通块数量、最大连通块面积、或改变连通块标记。BFS 使用队列代替递归,避免了递归深度过大的风险。
模板(四方向,统计面积)
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
int n, m;
int grid[MAXN][MAXN]; // 地图,1表示陆地/目标区域
bool vis[MAXN][MAXN];
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
// BFS 返回连通块大小
int bfs(int sx, int sy) {
queue<pair<int, int>> q;
q.push({sx, sy});
vis[sx][sy] = true;
int area = 0;
while (!q.empty()) {
auto [x, y] = q.front(); q.pop();
area++; // 每出队一个格子,面积加一
for (int i = 0; i < 4; ++i) {
int nx = x + dx[i];
int ny = y + dy[i];
// 边界检查、障碍检查、未访问检查
if (nx >= 0 && nx < n && ny >= 0 && ny < m
&& !vis[nx][ny] && grid[nx][ny] == 1) {
vis[nx][ny] = true;
q.push({nx, ny});
}
}
}
return area;
}
int main() {
// 输入 n, m, grid ...
memset(vis, 0, sizeof(vis));
int blocks = 0, maxArea = 0;
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (!vis[i][j] && grid[i][j] == 1) {
blocks++;
maxArea = max(maxArea, bfs(i, j));
}
}
}
cout << "连通块数量: " << blocks << endl;
cout << "最大面积: " << maxArea << endl;
return 0;
}
模板要点 队列:存储待访问的坐标,queue<pair<int, int>> 或自定义结构体。
入队即标记:在将新格子 push 进队列前就设置 vis[nx][ny] = true,防止同一格子被重复入队。
面积统计:在出队时计数,或入队时计数均可,效果一致。
优势:网格极大时(如 1000×1000),BFS 不会因递归栈溢出,比 DFS 更安全。
二、最小步数 / 最短路径问题 核心思想:BFS 按“距离起点步数”分层扩展,首次到达终点的步数即为最短步数。这是 BFS 最典型的应用。
- 基础模板:求最短路径长度(无权图)
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
int n, m;
int maze[MAXN][MAXN]; // 0可走,1障碍
int dist[MAXN][MAXN]; // 同时充当 vis 和 距离记录
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
int bfs(int sx, int sy, int ex, int ey) {
// 初始化距离数组为 -1 表示未访问
memset(dist, -1, sizeof(dist));
queue<pair<int, int>> q;
q.push({sx, sy});
dist[sx][sy] = 0;
while (!q.empty()) {
auto [x, y] = q.front(); q.pop();
// 如果到达终点,直接返回距离(因为 BFS 首次到达即最短)
if (x == ex && y == ey) {
return dist[x][y];
}
for (int i = 0; i < 4; ++i) {
int nx = x + dx[i];
int ny = y + dy[i];
if (nx >= 0 && nx < n && ny >= 0 && ny < m
&& maze[nx][ny] == 0 && dist[nx][ny] == -1) {
dist[nx][ny] = dist[x][y] + 1;
q.push({nx, ny});
}
}
}
return -1; // 无法到达
}
int main() {
// 输入 n, m, maze, 起点(sx,sy), 终点(ex,ey)
int steps = bfs(sx, sy, ex, ey);
if (steps == -1) cout << "无法到达" << endl;
else cout << "最短步数: " << steps << endl;
return 0;
}
- 进阶:记录最短路径本身(前驱节点) 若需要输出具体路径,可维护一个 prev 数组记录每个格子的来源。
pair<int, int> pre[MAXN][MAXN]; // 记录前驱坐标
void printPath(int ex, int ey) {
vector<pair<int, int>> path;
int x = ex, y = ey;
while (pre[x][y] != make_pair(-1, -1)) { // 起点标记
path.push_back({x, y});
auto [px, py] = pre[x][y];
x = px; y = py;
}
path.push_back({x, y}); // 起点
reverse(path.begin(), path.end());
for (auto p : path) cout << "(" << p.first << "," << p.second << ") ";
}
在 BFS 扩展时设置前驱:
cpp
if (dist[nx][ny] == -1) {
dist[nx][ny] = dist[x][y] + 1;
pre[nx][ny] = {x, y}; // 记录从 (x,y) 到达 (nx,ny)
q.push({nx, ny});
}
模板要点 距离数组 dist:初始化为 -1,同时承担 vis 功能。
终点判断:在出队时检查终点,可保证最短性;也可在扩展邻居时判断,略微提前。
时间复杂度:每个节点入队一次出队一次,O(V+E),非常高效。
适用性:仅适用于边权为 1 或边权均等的图。有权图最短路径需用 Dijkstra。
三、综合 BFS 题目 综合题可能涉及多状态、多维度、或带钥匙/门的迷宫等。核心是在 BFS 状态中增加维度来描述不同情形。
示例:带钥匙和门的迷宫(状态压缩 BFS) 题目:网格中有起点、终点、墙、钥匙(a-f)和门(A-F),拿到对应钥匙才能开门。求最短步数。
思路:BFS 的状态不仅是坐标,还有当前持有的钥匙集合(用二进制位表示)。vis[x][y][keys] 三维标记。
struct State {
int x, y, keys, steps;
};
int bfs(vector<string>& grid, int sx, int sy) {
int n = grid.size(), m = grid[0].size();
bool vis[35][35][1 << 6] = {false}; // 假设最多6把钥匙
queue<State> q;
q.push({sx, sy, 0, 0});
vis[sx][sy][0] = true;
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
while (!q.empty()) {
auto [x, y, keys, steps] = q.front(); q.pop();
if (grid[x][y] == 'E') return steps; // 到达终点
for (int i = 0; i < 4; ++i) {
int nx = x + dx[i];
int ny = y + dy[i];
if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
char c = grid[nx][ny];
if (c == '#') continue;
int newKeys = keys;
if (c >= 'a' && c <= 'f') {
newKeys |= (1 << (c - 'a')); // 捡钥匙
}
if (c >= 'A' && c <= 'F') {
// 检查是否有对应的钥匙
if (!(newKeys & (1 << (c - 'A')))) continue;
}
if (!vis[nx][ny][newKeys]) {
vis[nx][ny][newKeys] = true;
q.push({nx, ny, newKeys, steps + 1});
}
}
}
return -1;
}
其他常见综合 BFS 变种 多起点 BFS:所有起点同时入队,初始距离为 0,常用于“感染传播”或“多元最短路径”。
01 BFS:边权仅为 0 或 1 的最短路,用双端队列 deque 代替普通队列,遇到 0 边权放队首,1 边权放队尾。
双向 BFS:从起点和终点同时 BFS,当两个搜索相遇时结束,常用于状态空间巨大但起点终点明确的问题(如单词接龙)。
四、BFS 与 DFS 对比总结 问题类型 推荐算法 理由 连通块 / Flood Fill 均可 两者时间复杂度相同,BFS 更安全(无栈溢出),DFS 代码更简洁 最短路径 / 最小步数 BFS BFS 首次到达即最优,无需全局比较;DFS 需要搜索所有路径并记录最小值 找一条可行路径 均可 DFS 回溯易输出路径,BFS 需额外记录前驱 排列组合 / 回溯枚举 DFS 回溯树深度优先自然模拟决策过程,BFS 队列会存储大量中间状态,不实用 状态空间搜索(带状态) BFS/DFS 根据需求,求最短路用 BFS,求任意解或遍历所有可能用 DFS