一、BFS算法核心概念
广度优先搜索(Breadth-First Search) 是一种基于队列实现的图遍历算法,其核心特性是按层级逐步扩展搜索。主要特征包括:
- 逐层遍历 - 先访问起点相邻节点,再访问下一层节点
- 队列实现 - 使用FIFO(先进先出)数据结构
- 无回溯机制 - 每个节点仅访问一次
- 最短路径保证 - 在无权图中能找到两点间最短路径
二、BFS算法框架(伪代码)
def BFS(start, target):
# 初始化队列和访问集合
queue = collections.deque([start])
visited = set([start])
step = 0 # 记录步数
while queue:
size = len(queue)
# 遍历当前层级所有节点
for i in range(size):
cur = queue.popleft()
# 判断是否到达目标
if cur == target:
return step
# 将相邻未访问节点入队
for next in get_neighbors(cur):
if next not in visited:
visited.add(next)
queue.append(next)
# 层级增加
step += 1
return -1 # 未找到目标
三、BFS四大应用场景
1. 最短路径问题(无权图)
2. 连通分量问题
3. 层级遍历问题
4. 状态转换问题
四、BFS经典实现(C++)
// 迷宫最短路径(LeetCode 490改编)
#include <vector>
#include <queue>
using namespace std;
int shortestPath(vector<vector<int>>& grid, pair<int,int> start, pair<int,int> target) {
const vector<vector<int>> dirs = {{-1,0}, {0,1}, {1,0}, {0,-1}};
const int m = grid.size(), n = grid[0].size();
// 方向数组:上右下左
queue<pair<int,int>> q;
q.push(start);
grid[start.first][start.second] = -1; // 标记已访问
int steps = 0;
while (!q.empty()) {
int size = q.size();
for (int i = 0; i < size; i++) {
auto [x, y] = q.front(); q.pop();
// 到达目标点
if (x == target.first && y == target.second)
return steps;
// 四方向探索
for (auto& d : dirs) {
int nx = x + d[0], ny = y + d[1];
// 边界检查 + 可通行 + 未访问
if (nx >= 0 && nx < m && ny >= 0 && ny < n
&& grid[nx][ny] == 0) {
grid[nx][ny] = -1; // 标记访问
q.push({nx, ny});
}
}
}
steps++;
}
return -1; // 无法到达目标
}
五、BFS优化技巧
- 双向BFS - 从起点和终点同时开始搜索(LeetCode 127)
def two_ended_BFS(begin, end, wordList):
if end not in wordList: return 0
front = {begin}
back = {end}
dist = 1
wordSet = set(wordList)
while front and back:
# 总是从较小的一端扩展
if len(front) > len(back):
front, back = back, front
new_front = set()
for word in front:
for i in range(len(word)):
for c in 'abcdefghijklmnopqrstuvwxyz':
new_word = word[:i] + c + word[i+1:]
if new_word in back:
return dist + 1
if new_word in wordSet:
new_front.add(new_word)
wordSet.remove(new_word)
front = new_front
dist += 1
return 0
- 优先级队列优化 - 带权图变形(如Dijkstra算法)
- 状态压缩 - 减少内存使用(LeetCode 847)
- 层级剪枝 - 提前终止不必要的分支
六、BFS与DFS对比分析
特性 | BFS | DFS |
---|---|---|
数据结构 | 队列 | 栈 |
空间复杂度 | O(b^d) | O(bd) |
最优解 | 保证(无权图) | 不一定保证 |
实现方式 | 队列显式实现 | 递归/栈实现 |
适用场景 | 最短路径、层级问题 | 路径存在性、回溯问题 |
存储模式 | 按层级存储 | 深度优先存储 |
注:b为分支因子,d为深度
七、BFS常见错误及避免
-
遗漏已访问标记:导致节点重复访问
- 解决:在入队时立即标记节点
-
内存溢出:处理大状态空间
- 解决:使用双向BFS或IDA*优化
-
层级计数错误:在循环外部计数
- 解决:在每层循环前获取当前队列大小
-
方向遍历不完整:缺少某个搜索方向
- 解决:使用方向数组统一管理
# 正确层级计数示例
steps = 0
while queue:
size = len(queue)
for i in range(size): # 固定当前层大小
cur = queue.popleft()
# 处理节点...
steps += 1 # 层级递增
八、典型题目解析(LeetCode 102. 二叉树层序遍历)
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
if (!root) return {};
vector<vector<int>> result;
queue<TreeNode*> q;
q.push(root);
while (!q.empty()) {
int size = q.size();
vector<int> level;
for (int i = 0; i < size; i++) {
TreeNode* cur = q.front(); q.pop();
level.push_back(cur->val);
if (cur->left) q.push(cur->left);
if (cur->right) q.push(cur->right);
}
result.push_back(level);
}
return result;
}
};
算法过程:
- 创建队列,根节点入队
- 当队列非空时:
a. 记录当前队列大小(当前层级节点数)
b. 循环处理当前层级所有节点
c. 节点值存入临时列表
d. 将左右子节点入队 - 将当前层级结果加入最终结果集
- 返回结果集
九、扩展学习资源
-
图书:
- 《算法导论》第22章:图算法
- 《算法(第4版)》第4章:图算法
-
在线课程:
- Coursera《Algorithms, Part II》- Princeton University
- 牛客网《ACM竞赛算法精讲》
-
经典题目训练:
- LeetCode BFS专题(25题+)
- CodeForces Div2 B/C 级别题目
- 《算法竞赛入门经典》第4章
> 百看不如一练:尝试用BFS解决LeetCode 773. 滑动谜题