算法006:广度优先搜索(Breadth-First Search, BFS)
·
广度优先搜索算法
概述
广度优先搜索(Breadth-First Search, BFS)是一种用于遍历或搜索树和图的算法。该算法从指定的起始节点开始,逐层向外扩展,先访问所有距离起始节点为1的节点,然后是距离为2的节点,依此类推。BFS是图论中最基础和最重要的算法之一,由Konrad Zuse在1945年首次提出,后来被Edward F. Moore在1959年重新发现并推广。
算法描述
BFS使用队列数据结构来实现逐层搜索:
- 初始化:将起始节点加入队列,并标记为已访问
- 循环处理:当队列不为空时:
- 从队列中取出一个节点
- 处理该节点(如记录路径、检查是否为目标等)
- 将该节点的所有未访问邻居加入队列,并标记为已访问
步骤详解
- 创建一个队列和一个已访问集合
- 将起始节点加入队列,并标记为已访问
- 当队列不为空:
- 从队列前端取出一个节点
current - 处理
current节点 - 对于
current的每个邻居neighbor:- 如果
neighbor未被访问:- 将
neighbor标记为已访问 - 将
neighbor加入队列
- 将
- 如果
- 从队列前端取出一个节点
- 当队列为空时,搜索结束
数学基础
时间复杂度
- 时间复杂度:O(V+E)O(V + E)O(V+E) - 其中 VVV 是顶点数量,EEE 是边数量
- 每个顶点和边最多被访问一次
空间复杂度
- 空间复杂度:O(V)O(V)O(V) - 最坏情况下需要存储所有顶点
数学分析
BFS的时间复杂度分析:
- 访问所有顶点:O(V)O(V)O(V)
- 遍历所有边:O(E)O(E)O(E)
- 总时间复杂度:O(V+E)O(V + E)O(V+E)
BFS的空间复杂度分析:
- 队列最坏情况下存储所有顶点:O(V)O(V)O(V)
- 已访问集合存储所有顶点:O(V)O(V)O(V)
- 总空间复杂度:O(V)O(V)O(V)
实现
Python实现
from collections import deque
def bfs(graph, start):
"""
广度优先搜索实现
参数:
graph: 图的邻接表表示
start: 起始节点
返回:
访问顺序列表
"""
visited = set()
queue = deque([start])
visited.add(start)
result = []
while queue:
current = queue.popleft()
result.append(current)
for neighbor in graph[current]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
return result
def bfs_shortest_path(graph, start, end):
"""
使用BFS查找最短路径
参数:
graph: 图的邻接表表示
start: 起始节点
end: 目标节点
返回:
最短路径和路径长度
"""
if start == end:
return [start], 0
visited = set()
queue = deque([(start, [start])])
visited.add(start)
while queue:
current, path = queue.popleft()
for neighbor in graph[current]:
if neighbor == end:
return path + [neighbor], len(path)
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, path + [neighbor]))
return None, float('infinity')
def bfs_all_shortest_paths(graph, start):
"""
使用BFS查找从起始节点到所有其他节点的最短路径
参数:
graph: 图的邻接表表示
start: 起始节点
返回:
包含最短距离和前驱节点的字典
"""
distances = {node: float('infinity') for node in graph}
distances[start] = 0
previous = {node: None for node in graph}
queue = deque([start])
while queue:
current = queue.popleft()
for neighbor in graph[current]:
if distances[neighbor] == float('infinity'):
distances[neighbor] = distances[current] + 1
previous[neighbor] = current
queue.append(neighbor)
return distances, previous
# 示例图
example_graph = {
'A': ['B', 'C'],
'B': ['A', 'D', 'E'],
'C': ['A', 'F'],
'D': ['B'],
'E': ['B', 'F'],
'F': ['C', 'E']
}
# 使用示例
print("BFS访问顺序:", bfs(example_graph, 'A'))
path, distance = bfs_shortest_path(example_graph, 'A', 'F')
print(f"从A到F的最短路径: {path}, 距离: {distance}")
distances, previous = bfs_all_shortest_paths(example_graph, 'A')
print("\n从A出发到各节点的最短距离:")
for node in sorted(distances.keys()):
print(f"{node}: {distances[node]}")
C++实现
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
#include <unordered_set>
#include <algorithm>
using namespace std;
// BFS实现
vector<string> bfs(const unordered_map<string, vector<string>>& graph, const string& start) {
unordered_set<string> visited;
queue<string> q;
vector<string> result;
q.push(start);
visited.insert(start);
while (!q.empty()) {
string current = q.front();
q.pop();
result.push_back(current);
for (const string& neighbor : graph.at(current)) {
if (visited.find(neighbor) == visited.end()) {
visited.insert(neighbor);
q.push(neighbor);
}
}
}
return result;
}
// 查找最短路径
pair<vector<string>, int> bfs_shortest_path(const unordered_map<string, vector<string>>& graph,
const string& start, const string& end) {
if (start == end) {
return {{start}, 0};
}
unordered_set<string> visited;
queue<pair<string, vector<string>>> q;
visited.insert(start);
q.push({start, {start}});
while (!q.empty()) {
auto current = q.front();
q.pop();
string current_node = current.first;
vector<string> path = current.second;
for (const string& neighbor : graph.at(current_node)) {
if (neighbor == end) {
vector<string> full_path = path;
full_path.push_back(neighbor);
return {full_path, static_cast<int>(full_path.size() - 1)};
}
if (visited.find(neighbor) == visited.end()) {
visited.insert(neighbor);
vector<string> new_path = path;
new_path.push_back(neighbor);
q.push({neighbor, new_path});
}
}
}
return {{}, -1};
}
// 查找所有最短路径
pair<unordered_map<string, int>, unordered_map<string, string>> bfs_all_shortest_paths(
const unordered_map<string, vector<string>>& graph, const string& start) {
unordered_map<string, int> distances;
unordered_map<string, string> previous;
for (const auto& node : graph) {
distances[node.first] = INT_MAX;
previous[node.first] = "";
}
distances[start] = 0;
queue<string> q;
q.push(start);
while (!q.empty()) {
string current = q.front();
q.pop();
for (const string& neighbor : graph.at(current)) {
if (distances[neighbor] == INT_MAX) {
distances[neighbor] = distances[current] + 1;
previous[neighbor] = current;
q.push(neighbor);
}
}
}
return {distances, previous};
}
int main() {
// 示例图
unordered_map<string, vector<string>> example_graph = {
{"A", {"B", "C"}},
{"B", {"A", "D", "E"}},
{"C", {"A", "F"}},
{"D", {"B"}},
{"E", {"B", "F"}},
{"F", {"C", "E"}}
};
// 使用示例
cout << "BFS访问顺序: ";
vector<string> bfs_result = bfs(example_graph, "A");
for (size_t i = 0; i < bfs_result.size(); i++) {
if (i > 0) cout << " -> ";
cout << bfs_result[i];
}
cout << endl;
auto path_result = bfs_shortest_path(example_graph, "A", "F");
cout << "从A到F的最短路径: ";
for (size_t i = 0; i < path_result.first.size(); i++) {
if (i > 0) cout << " -> ";
cout << path_result.first[i];
}
cout << ", 距离: " << path_result.second << endl;
auto all_paths = bfs_all_shortest_paths(example_graph, "A");
cout << "\n从A出发到各节点的最短距离:" << endl;
for (const auto& node : all_paths.first) {
cout << node.first << ": " << node.second << endl;
}
return 0;
}
变体和优化
1. 双向BFS
def bidirectional_bfs(graph, start, end):
"""
双向BFS实现
参数:
graph: 图的邻接表表示
start: 起始节点
end: 目标节点
返回:
最短路径和路径长度
"""
if start == end:
return [start], 0
# 前向搜索
forward_queue = deque([(start, [start])])
forward_visited = {start}
# 后向搜索
backward_queue = deque([(end, [end])])
backward_visited = {end}
while forward_queue and backward_queue:
# 前向搜索一步
if forward_queue:
current, path = forward_queue.popleft()
for neighbor in graph[current]:
if neighbor in backward_visited:
# 找到交汇点
backward_path = None
for (b_node, b_path) in backward_queue:
if b_node == neighbor:
backward_path = b_path
break
if backward_path:
full_path = path + backward_path[::-1]
return full_path, len(full_path) - 1
if neighbor not in forward_visited:
forward_visited.add(neighbor)
forward_queue.append((neighbor, path + [neighbor]))
# 后向搜索一步
if backward_queue:
current, path = backward_queue.popleft()
for neighbor in graph[current]:
if neighbor in forward_visited:
# 找到交汇点
forward_path = None
for (f_node, f_path) in forward_queue:
if f_node == neighbor:
forward_path = f_path
break
if forward_path:
full_path = forward_path + path[::-1]
return full_path, len(full_path) - 1
if neighbor not in backward_visited:
backward_visited.add(neighbor)
backward_queue.append((neighbor, path + [neighbor]))
return None, float('infinity')
2. 有限深度的BFS
def limited_bfs(graph, start, max_depth):
"""
有限深度的BFS实现
参数:
graph: 图的邻接表表示
start: 起始节点
max_depth: 最大搜索深度
返回:
在指定深度范围内的所有节点
"""
visited = set()
queue = deque([(start, 0)]) # (node, depth)
visited.add(start)
result = []
while queue:
current, depth = queue.popleft()
if depth <= max_depth:
result.append((current, depth))
for neighbor in graph[current]:
if neighbor not in visited:
visited.add(neighbor)
queue.append((neighbor, depth + 1))
return result
3. 优先级BFS
import heapq
def priority_bfs(graph, start, priorities):
"""
基于优先级的BFS实现
参数:
graph: 图的邻接表表示
start: 起始节点
priorities: 节点优先级字典
返回:
访问顺序列表
"""
visited = set()
# 使用优先队列,优先级低的先访问
priority_queue = []
heapq.heappush(priority_queue, (priorities[start], start))
visited.add(start)
result = []
while priority_queue:
_, current = heapq.heappop(priority_queue)
result.append(current)
for neighbor in graph[current]:
if neighbor not in visited:
visited.add(neighbor)
heapq.heappush(priority_queue, (priorities[neighbor], neighbor))
return result
应用场景
BFS广泛应用于:
- 最短路径查找:在无权图中寻找最短路径
- 网络爬虫:爬取网页链接
- 社交网络分析:查找社交关系网络
- 人工智能:状态空间搜索
- 游戏开发:寻找可达区域
优点和缺点
优点
- 保证找到最短路径:在无权图中保证找到最短路径
- 实现简单:算法逻辑清晰,易于实现
- 空间效率高:相比DFS,在某些情况下空间效率更高
- 适合广度优先场景:适合逐层扩展的搜索场景
- 可预测性:搜索过程具有可预测性
缺点
- 空间消耗大:在最坏情况下需要存储大量节点
- 不适合深度优先场景:对于深度较大的图效率较低
- 内存密集:需要维护队列和已访问集合
- 不适用于加权图:不能直接用于寻找加权图中的最短路径
- 分支因子大时性能差:当图的分支因子很大时性能下降
性能比较
| 算法 | 时间复杂度 | 空间复杂度 | 最优性 | 适用场景 |
|---|---|---|---|---|
| BFS | O(V+E)O(V + E)O(V+E) | O(V)O(V)O(V) | 是(无权图) | 无权图最短路径 |
| DFS | O(V+E)O(V + E)O(V+E) | O(V)O(V)O(V) | 否 | 深度优先搜索 |
| Dijkstra | O((V+E)logV)O((V + E) \log V)O((V+E)logV) | O(V+E)O(V + E)O(V+E) | 是 | 加权图最短路径 |
| A* | O(bd)O(b^d)O(bd) | O(bd)O(b^d)O(bd) | 是(可容性) | 启发式搜索 |
| Bellman-Ford | O(VE)O(VE)O(VE) | O(V)O(V)O(V) | 是 | 支持负权重 |
实际应用示例
示例1:社交网络中的最短路径
def social_network_example():
"""
社交网络中的最短路径示例
"""
# 社交网络图:人名到朋友列表的映射
social_network = {
'Alice': ['Bob', 'Charlie'],
'Bob': ['Alice', 'David', 'Eve'],
'Charlie': ['Alice', 'Frank'],
'David': ['Bob'],
'Eve': ['Bob', 'Frank'],
'Frank': ['Charlie', 'Eve']
}
# 查找Alice和Frank之间的最短社交路径
path, distance = bfs_shortest_path(social_network, 'Alice', 'Frank')
if path:
print(f"Alice和Frank之间的最短社交路径:")
for i, person in enumerate(path):
if i > 0:
print(" -> ", end="")
print(person, end="")
print(f"\n社交距离: {distance} (表示中间有 {distance-1} 个人)")
else:
print("Alice和Frank之间没有社交路径")
# 查找Alice的所有社交距离
distances, _ = bfs_all_shortest_paths(social_network, 'Alice')
print("\nAlice的社交网络:")
for person in sorted(distances.keys()):
if person != 'Alice':
print(f"{person}: 社交距离 {distances[person]}")
social_network_example()
示例2:网页爬虫
def web_crawler_example():
"""
网页爬虫示例
"""
# 模拟网页链接结构
web_pages = {
'首页': ['新闻', '产品', '关于我们'],
'新闻': ['首页', '体育新闻', '科技新闻'],
'产品': ['首页', '产品A', '产品B'],
'关于我们': ['首页', '团队', '联系方式'],
'体育新闻': ['新闻'],
'科技新闻': ['新闻', '人工智能', '区块链'],
'产品A': ['产品'],
'产品B': ['产品'],
'团队': ['关于我们'],
'联系方式': ['关于我们'],
'人工智能': ['科技新闻'],
'区块链': ['科技新闻']
}
# 从首页开始爬取
print("从首页开始爬取所有页面:")
crawled_pages = bfs(web_pages, '首页')
for i, page in enumerate(crawled_pages):
if i > 0:
print(" -> ", end="")
print(page, end="")
print()
# 查找从首页到人工智能的最短路径
path, distance = bfs_shortest_path(web_pages, '首页', '人工智能')
print(f"\n从首页到人工智能的最短路径:")
for i, page in enumerate(path):
if i > 0:
print(" -> ", end="")
print(page, end="")
print(f"\n点击次数: {distance}")
web_crawler_example()
示例3:迷宫寻路
def maze_bfs_example():
"""
迷宫寻路示例
"""
# 迷宫:0表示可通过,1表示墙壁
maze = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 0, 0, 0, 0, 0, 1, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 1, 1, 1, 1, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 1, 0, 0, 0],
[0, 1, 1, 1, 1, 0, 1, 0, 1, 0],
[0, 0, 0, 0, 1, 0, 1, 0, 0, 0],
[0, 0, 1, 0, 1, 0, 1, 1, 1, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 1, 0, 0, 0, 0, 0, 1, 0]
]
# 将迷宫转换为图的邻接表表示
def maze_to_graph(maze):
rows = len(maze)
cols = len(maze[0])
graph = {}
for i in range(rows):
for j in range(cols):
if maze[i][j] == 0: # 可通过的位置
node = f"{i},{j}"
graph[node] = []
# 检查四个方向
directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
for dx, dy in directions:
ni, nj = i + dx, j + dy
if 0 <= ni < rows and 0 <= nj < cols and maze[ni][nj] == 0:
neighbor = f"{ni},{nj}"
graph[node].append(neighbor)
return graph
maze_graph = maze_to_graph(maze)
# 起点和终点
start = "0,0"
end = "9,9"
# 使用BFS寻找最短路径
path, distance = bfs_shortest_path(maze_graph, start, end)
if path:
print(f"从 {start} 到 {end} 的最短路径:")
for i, pos in enumerate(path):
if i > 0:
print(" -> ", end="")
print(pos, end="")
print(f"\n最短步数: {distance}")
# 在迷宫中显示路径
print("\n迷宫路径显示 (P表示路径):")
path_set = set(path)
for i in range(len(maze)):
for j in range(len(maze[0])):
pos = f"{i},{j}"
if pos == start:
print("S", end=" ")
elif pos == end:
print("E", end=" ")
elif pos in path_set:
print("P", end=" ")
elif maze[i][j] == 1:
print("#", end=" ")
else:
print(".", end=" ")
print()
else:
print(f"从 {start} 到 {end} 没有可行路径")
maze_bfs_example()
结论
广度优先搜索是图论中最基础和最重要的算法之一。它通过逐层扩展的方式遍历图,保证在无权图中找到最短路径。BFS的简单性和可预测性使其成为许多图算法的基础。
从社交网络分析到网页爬虫,从迷宫寻路到人工智能的状态空间搜索,BFS的身影无处不在。虽然BFS在某些场景下可能不如其他算法高效,但其保证最优性和简单实现使其成为计算机科学教育中的经典算法。
BFS的思想不仅限于图遍历,还广泛应用于各种需要逐层处理的算法设计中。其队列数据结构的使用也为许多其他算法提供了灵感。
更多推荐


所有评论(0)