前言
在计算机科学中,图的遍历是解决许多问题的基础,尤其是在路径查找、图搜索和网络流等领域。广度优先搜索(BFS)是一种常用的图遍历算法,通过逐层探索图的节点,能够有效地找到最短路径。然而,在某些情况下,尤其是在大规模图中,单向 BFS 的效率可能会降低。为了解决这一问题,双向 BFS 应运而生。双向 BFS 通过同时从起点和终点进行搜索,在探索过程中减少了搜索的空间和时间复杂度。本文将探讨双向 BFS 的基本原理及其在 CSP-J/S 复赛中的应用,重点介绍其在路径查找中的优势和实现细节。
双向 BFS(Bidirectional BFS)
双向 BFS 是一种在图中查找最短路径的算法,通过从起点和终点同时进行广度优先搜索(BFS),有效地缩短搜索时间。与单向 BFS 不同,双向 BFS 通过两个搜索树的交汇点来优化路径查找,通常可以显著减少搜索空间。
遍历过程
-
初始化:
- 从起点和终点分别初始化两个队列(
queue_start
和queue_end
)。 - 分别维护两个集合(
visited_start
和visited_end
)以记录访问过的节点。
- 从起点和终点分别初始化两个队列(
-
搜索过程:
- 在每一步中,首先从
queue_start
进行搜索,探索当前节点的邻居并将其添加到visited_start
中。 - 然后从
queue_end
进行搜索,探索当前节点的邻居并将其添加到visited_end
中。 - 如果在两个集合中找到相同的节点,说明两个搜索路径相交,从而可以构建出从起点到终点的最短路径。
- 在每一步中,首先从
-
结束条件:
- 如果
queue_start
和queue_end
中有任何一个队列为空,算法结束,无法找到路径。
- 如果
双向 BFS 与单向 BFS 的区别
-
搜索方向:
- 单向 BFS 只从起点开始向外扩展,直到找到终点。
- 双向 BFS 同时从起点和终点开始向中间扩展。
-
搜索效率:
- 单向 BFS 的时间复杂度为 (O(V + E)),其中 (V) 是图中的顶点数量,(E) 是边的数量。
- 双向 BFS 在理想情况下可以将搜索复杂度减少到 (O(\sqrt{V})),因为两个搜索同时进行,从而在交汇点时减少了探索的节点数量。
示例
考虑以下简单无向图:
A
/ | \
B | C
\ | /
D
/ | \
E | F
\ | /
G
节点连接:
- A → B, C, D
- B → A, D
- C → A, D
- D → A, B, C, E, F
- E → D, G
- F → D, G
- G → E, F
目标:从 A 到 G
单向 BFS 遍历过程
- 从 A 开始,访问节点 A。
- 将 A 的邻居 B, C, D 加入队列。
- 从队列中取出 B,访问节点 B,加入 D(已访问)跳过,加入 D 的邻居 E。
- 从队列中取出 C,访问节点 C,加入 D(已访问)跳过。
- 从队列中取出 D,访问节点 D,加入 E 和 F。
- 从队列中取出 E,访问 E,加入 G。
- 从队列中取出 F,访问 F,加入 G(已访问)跳过。
- 从队列中取出 G,目标找到,遍历结束。
路径:A → D → E → G(最短路径为 3)
双向 BFS 遍历过程
- 从 A 开始,访问节点 A,加入邻居 B, C, D 到
queue_start
。 - 从 G 开始,访问节点 G,加入邻居 E, F 到
queue_end
。 - 访问
queue_start
中的 D,将邻居 A, B, C 加入(已访问跳过)。 - 访问
queue_end
中的 F,将邻居 D 加入(已访问跳过),找到 D。 - 发现 D 在
visited_start
和visited_end
中都已访问,路径交汇。
路径:A → D → G(最短路径为 2)
双向 BFS 通过同时从起点和终点进行搜索,能够更高效地找到最短路径,相比单向 BFS,在搜索大图时具有显著的优势。
C++ 实现双向BFS
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_set>
#include <unordered_map>
using namespace std;
class Graph {
public:
// 使用邻接表表示图
unordered_map<int, vector<int>> adjList;
void addEdge(int u, int v) {
adjList[u].push_back(v);
adjList[v].push_back(u); // 无向图
}
// 双向 BFS 寻找最短路径
int bidirectionalBFS(int start, int goal) {
if (start == goal) return 0;
// 初始化队列和访问集合
queue<int> queueStart, queueEnd;
unordered_set<int> visitedStart, visitedEnd;
// 从起点和终点开始
queueStart.push(start);
queueEnd.push(goal);
visitedStart.insert(start);
visitedEnd.insert(goal);
int distance = 0;
while (!queueStart.empty() && !queueEnd.empty()) {
distance++;
// 处理从起点的搜索
if (expandLayer(queueStart, visitedStart, visitedEnd)) {
return distance;
}
// 处理从终点的搜索
if (expandLayer(queueEnd, visitedEnd, visitedStart)) {
return distance;
}
}
return -1; // 无法到达目标
}
private:
// 扩展当前层
bool expandLayer(queue<int>& currentQueue, unordered_set<int>& currentVisited, unordered_set<int>& otherVisited) {
int size = currentQueue.size();
for (int i = 0; i < size; i++) {
int node = currentQueue.front();
currentQueue.pop();
// 遍历邻居
for (int neighbor : adjList[node]) {
if (otherVisited.count(neighbor)) {
return true; // 找到交汇点
}
if (currentVisited.insert(neighbor).second) {
// 插入成功
currentQueue.push(neighbor);
}
}
}
return false; // 没有找到交汇点
}
};
int main() {
Graph graph;
// 添加边
graph.addEdge(1, 2);
graph.addEdge(1, 3);
graph.addEdge(2, 4);
graph.addEdge(3, 4);
graph.addEdge(4, 5);
graph.addEdge(5, 6);
int start = 1;
int goal = 6;
int result = graph.bidirectionalBFS(start, goal);
if (result != -1) {
cout << "最短路径长度: " << result << endl;
} else {
cout << "无法到达目标节点" << endl;
}
return 0;
}
总结
双向 BFS 是一种高效的图遍历算法,适用于寻找最短路径的问题。在处理大型图时,它能够显著减少搜索的时间和空间复杂度,尤其在起点和终点之间的距离较远时。通过从两个方向同时搜索,双向 BFS 能够快速找到交汇点,从而加快搜索速度。在 CSP-J/S 复赛中,双向 BFS 的应用使得解决复杂路径问题变得更加高效,成为参赛选手的重要工具。未来的研究可以进一步探讨如何优化双向 BFS 的实现,甚至结合其他算法以应对更复杂的图结构和搜索需求。