广度优先搜索(BFS)是图论中最基础的遍历算法之一,其核心思想如同涟漪扩散般逐层探索。想象你站在迷宫入口处,先探索所有一步可达的位置,再探索两步可达的位置,依此类推直到找到出口。这种策略保证了首次到达目标时的路径必然是最短路径。
BFS的标准实现需要三个关键组件:
典型BFS模板如下:
cpp复制void bfs(Node start) {
queue<Node> q;
unordered_set<Node> visited;
q.push(start);
visited.insert(start);
while (!q.empty()) {
Node current = q.front();
q.pop();
for (Node neighbor : getNeighbors(current)) {
if (!visited.count(neighbor)) {
visited.insert(neighbor);
q.push(neighbor);
}
}
}
}
关键理解:BFS的队列机制天然保证了节点的访问顺序是按照它们距离起点的层次递增的。这在无权图的最短路径问题中尤为重要。
课程表问题(LeetCode 207)本质是检测有向图是否存在环。拓扑排序的BFS实现(又称Kahn算法)通过不断移除入度为0的节点来判断图的拓扑可能性。
算法步骤分解:
cpp复制vector<int> indegrees(numCourses, 0);
vector<vector<int>> adj(numCourses);
// 构建入度表和邻接表
for (auto& edge : prerequisites) {
int to = edge[0], from = edge[1];
indegrees[to]++;
adj[from].push_back(to);
}
易错点:邻接表的构建方向容易混淆。记住拓扑排序是"先修课程指向后续课程",而有些问题可能需要反向建图。
时间复杂度:O(V+E)(节点数+边数)
空间复杂度:O(V+E)
实际编码时可以优化的点:
岛屿数量问题(LeetCode 200)将二维矩阵转化为无向图,每个"1"代表节点,相邻的"1"之间存在边。BFS的作用是完整标记一个连通分量中的所有节点。
算法框架:
cpp复制vector<pair<int, int>> dirs = {{-1,0}, {1,0}, {0,-1}, {0,1}};
while (!que.empty()) {
auto [x, y] = que.front();
que.pop();
for (auto [dx, dy] : dirs) {
int nx = x + dx, ny = y + dy;
if (nx >=0 && nx < rows && ny >=0 && ny < cols
&& grid[nx][ny] == '1') {
grid[nx][ny] = '0';
que.push({nx, ny});
}
}
}
实战技巧:使用方向数组使代码更整洁。C++17的结构化绑定([x,y])可提升代码可读性,但要注意编译器支持。
在岛屿问题中,BFS和DFS各有优劣:
内存消耗对比:
cpp复制queue<pair<int, int>> q;
q.emplace(1, 2); // 比push更高效
while (!q.empty()) {
auto [x, y] = q.front(); // C++17结构化绑定
q.pop();
// 处理逻辑...
}
性能注意事项:
cpp复制unordered_map<Node, int> distance;
distance.reserve(1000); // 预分配内存
auto it = distance.find(node);
if (it != distance.end()) {
// 存在性检查+访问一步完成
}
经验之谈:在算法竞赛中,当节点可以用整数表示时,用vector代替unordered_map可以获得10倍以上的性能提升。
症状:程序运行不终止
排查步骤:
典型错误原因:
当BFS超时时检查:
典型问题:矩阵中多个起点的最短距离计算
解决方案:初始化时将多个起点同时入队
cpp复制// 初始化所有起点
for (auto& start : starts) {
q.push(start);
visited[start] = 0;
}
需要记录层次信息时的写法:
cpp复制int level = 0;
while (!q.empty()) {
int size = q.size();
while (size--) {
auto current = q.front();
q.pop();
// 处理当前节点...
}
level++;
}
当状态可以用位表示时:
cpp复制queue<int> q;
unordered_set<int> visited;
int startState = 0;
q.push(startState);
visited.insert(startState);
在解决实际算法问题时,我发现BFS的实现质量往往取决于三个关键点:队列操作的准确性、状态标记的完整性和边界条件的严密性。特别是在竞赛环境中,使用静态数组代替STL容器有时能带来意想不到的性能提升。对于二维矩阵问题,将二维坐标压缩为一维索引(如index = x * cols + y)可以简化很多操作。