7-1 广度优先搜索
广度优先搜索(Breadth-First Search, BFS)
广度优先搜索(Breadth-First Search,简称 BFS)是一种基础的图遍历算法。它的核心思想是:从起始节点出发,先访问所有距离为 1 的邻居节点,再访问所有距离为 2 的邻居节点,依次向外扩展,直到所有可达节点都被访问。BFS 使用队列(Queue)作为核心数据结构,保证节点按层次顺序被处理。
BFS 最重要的性质是:在无权图(Unweighted Graph)中,BFS 能自然地找到从起点到其他所有可达节点的最短路径(Shortest Path)。
本文使用如下示例图(邻接表表示):
邻接表(Adjacency List):
0: [1, 2]
1: [0, 3]
2: [0, 3]
3: [1, 2, 4, 5]
4: [3, 5]
5: [3, 4]
图的结构:
0
/ \
1 2
\ /
3
/ \
4 5
从节点 0 出发,BFS 的遍历顺序为:先访问 0(第 0 层),然后访问 1, 2(第 1 层),再访问 3(第 2 层),最后访问 4, 5(第 3 层)。
Level 0: 0
Level 1: 1 2
Level 2: 3
Level 3: 4 5
BFS 遍历顺序: 0 1 2 3 4 5
图的表示
图(Graph)的常用存储方式是邻接表(Adjacency List)。对于每个节点,用一个列表存储它的所有邻居节点。不同语言的实现方式各有不同:C++ 使用 vector<vector<int>>,C 语言使用二维数组模拟,Python 使用列表的列表(List of Lists),Go 使用 [][]int 切片。
#include <iostream>
#include <vector>
using namespace std;
int main() {
// Build adjacency list for the example graph
// 6 nodes: 0-5
int n = 6;
vector<vector<int>> adj(n);
// Add edges (undirected graph, add both directions)
adj[0].push_back(1);
adj[0].push_back(2);
adj[1].push_back(0);
adj[1].push_back(3);
adj[2].push_back(0);
adj[2].push_back(3);
adj[3].push_back(1);
adj[3].push_back(2);
adj[3].push_back(4);
adj[3].push_back(5);
adj[4].push_back(3);
adj[4].push_back(5);
adj[5].push_back(3);
adj[5].push_back(4);
// Print adjacency list
for (int i = 0; i < n; i++) {
cout << i << ": ";
for (int neighbor : adj[i]) {
cout << neighbor << " ";
}
cout << endl;
}
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#define MAX_NODES 6
#define MAX_NEIGHBORS 4
int main() {
// Use 2D array to represent adjacency list
// adjCount[i] = number of neighbors of node i
int adj[MAX_NODES][MAX_NEIGHBORS] = {0};
int adjCount[MAX_NODES] = {0};
// Helper macro to add edge (both directions for undirected graph)
#define ADD_EDGE(u, v) do { \
adj[u][adjCount[u]++] = v; \
adj[v][adjCount[v]++] = u; \
} while(0)
// Build the example graph
ADD_EDGE(0, 1);
ADD_EDGE(0, 2);
ADD_EDGE(1, 3);
ADD_EDGE(2, 3);
ADD_EDGE(3, 4);
ADD_EDGE(3, 5);
ADD_EDGE(4, 5);
// Print adjacency list
for (int i = 0; i < MAX_NODES; i++) {
printf("%d: ", i);
for (int j = 0; j < adjCount[i]; j++) {
printf("%d ", adj[i][j]);
}
printf("\n");
}
return 0;
}
def main():
# Build adjacency list using list of lists
adj = {
0: [1, 2],
1: [0, 3],
2: [0, 3],
3: [1, 2, 4, 5],
4: [3, 5],
5: [3, 4],
}
# Print adjacency list
for node in sorted(adj.keys()):
print(f"{node}: {adj[node]}")
if __name__ == "__main__":
main()
package main
import "fmt"
func main() {
// Build adjacency list using slice of slices
adj := [][]int{
{1, 2}, // node 0
{0, 3}, // node 1
{0, 3}, // node 2
{1, 2, 4, 5}, // node 3
{3, 5}, // node 4
{3, 4}, // node 5
}
// Print adjacency list
for i, neighbors := range adj {
fmt.Printf("%d: %v\n", i, neighbors)
}
}
上述代码构建了示例图的邻接表表示。C++ 使用 vector<vector<int>> 动态管理邻接关系;C 语言用固定大小的二维数组 adj[MAX_NODES][MAX_NEIGHBORS] 配合计数数组 adjCount 来存储;Python 使用字典(Dictionary)嵌套列表,最为简洁;Go 使用 [][]int 切片,声明时直接初始化。
运行该程序将输出:
0: 1 2
1: 0 3
2: 0 3
3: 1 2 4 5
4: 3 5
5: 3 4
BFS 算法实现
BFS 的核心算法步骤如下:
- 创建一个队列(Queue),将起始节点入队
- 创建一个访问标记数组(Visited Array),标记起始节点为已访问
- 当队列不为空时,执行以下操作:
- 从队列头部取出一个节点(Dequeue),进行处理(如打印)
- 遍历该节点的所有邻居,将未访问过的邻居入队并标记为已访问
以示例图从节点 0 出发,逐步跟踪 BFS 过程:
初始: queue = [0], visited = {0}
步骤1: 出队0, 访问0, 邻居1,2未访问 → 入队 → queue = [1,2], visited = {0,1,2}
步骤2: 出队1, 访问1, 邻居0已访问,邻居3未访问 → 入队 → queue = [2,3], visited = {0,1,2,3}
步骤3: 出队2, 访问2, 邻居0已访问,邻居3已访问 → 无入队 → queue = [3]
步骤4: 出队3, 访问3, 邻居1,2已访问,邻居4,5未访问 → 入队 → queue = [4,5], visited = {0,1,2,3,4,5}
步骤5: 出队4, 访问4, 邻居3,5已访问 → 无入队 → queue = [5]
步骤6: 出队5, 访问5, 邻居3,4已访问 → 无入队 → queue = []
BFS 遍历结果: 0 1 2 3 4 5
C++ 实现
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
// BFS traversal from a starting node
void bfs(const vector<vector<int>>& adj, int start) {
int n = adj.size();
vector<bool> visited(n, false);
queue<int> q;
// Enqueue start node and mark as visited
q.push(start);
visited[start] = true;
while (!q.empty()) {
int node = q.front();
q.pop();
// Process the current node
cout << node << " ";
// Enqueue all unvisited neighbors
for (int neighbor : adj[node]) {
if (!visited[neighbor]) {
visited[neighbor] = true;
q.push(neighbor);
}
}
}
}
int main() {
// Build the example graph
vector<vector<int>> adj = {
{1, 2}, // node 0
{0, 3}, // node 1
{0, 3}, // node 2
{1, 2, 4, 5}, // node 3
{3, 5}, // node 4
{3, 4}, // node 5
};
cout << "BFS from node 0: ";
bfs(adj, 0);
cout << endl;
return 0;
}
C 实现
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_NODES 6
#define MAX_NEIGHBORS 4
// Simple queue implementation using circular array
typedef struct {
int data[MAX_NODES];
int front, rear;
} Queue;
void queueInit(Queue* q) {
q->front = 0;
q->rear = 0;
}
bool queueEmpty(Queue* q) {
return q->front == q->rear;
}
void queuePush(Queue* q, int val) {
q->data[q->rear++] = val;
}
int queuePop(Queue* q) {
return q->data[q->front++];
}
void bfs(int adj[][MAX_NEIGHBORS], int adjCount[], int start, int n) {
bool visited[MAX_NODES] = {false};
Queue q;
queueInit(&q);
// Enqueue start node and mark as visited
queuePush(&q, start);
visited[start] = true;
while (!queueEmpty(&q)) {
int node = queuePop(&q);
// Process the current node
printf("%d ", node);
// Enqueue all unvisited neighbors
for (int i = 0; i < adjCount[node]; i++) {
int neighbor = adj[node][i];
if (!visited[neighbor]) {
visited[neighbor] = true;
queuePush(&q, neighbor);
}
}
}
}
int main() {
int adj[MAX_NODES][MAX_NEIGHBORS] = {0};
int adjCount[MAX_NODES] = {0};
#define ADD_EDGE(u, v) do { \
adj[u][adjCount[u]++] = v; \
adj[v][adjCount[v]++] = u; \
} while(0)
ADD_EDGE(0, 1);
ADD_EDGE(0, 2);
ADD_EDGE(1, 3);
ADD_EDGE(2, 3);
ADD_EDGE(3, 4);
ADD_EDGE(3, 5);
ADD_EDGE(4, 5);
printf("BFS from node 0: ");
bfs(adj, adjCount, 0, MAX_NODES);
printf("\n");
return 0;
}
Python 实现
from collections import deque
def bfs(adj, start):
"""BFS traversal from a starting node."""
n = len(adj)
visited = [False] * n
q = deque()
# Enqueue start node and mark as visited
q.append(start)
visited[start] = True
while q:
node = q.popleft()
# Process the current node
print(node, end=" ")
# Enqueue all unvisited neighbors
for neighbor in adj[node]:
if not visited[neighbor]:
visited[neighbor] = True
q.append(neighbor)
if __name__ == "__main__":
adj = {
0: [1, 2],
1: [0, 3],
2: [0, 3],
3: [1, 2, 4, 5],
4: [3, 5],
5: [3, 4],
}
print("BFS from node 0:", end=" ")
bfs(adj, 0)
print()
Go 实现
package main
import "fmt"
// BFS traversal from a starting node
func bfs(adj [][]int, start int) {
n := len(adj)
visited := make([]bool, n)
// Use slice as a simple queue
queue := []int{start}
visited[start] = true
for len(queue) > 0 {
// Dequeue from front
node := queue[0]
queue = queue[1:]
// Process the current node
fmt.Print(node, " ")
// Enqueue all unvisited neighbors
for _, neighbor := range adj[node] {
if !visited[neighbor] {
visited[neighbor] = true
queue = append(queue, neighbor)
}
}
}
}
func main() {
adj := [][]int{
{1, 2}, // node 0
{0, 3}, // node 1
{0, 3}, // node 2
{1, 2, 4, 5}, // node 3
{3, 5}, // node 4
{3, 4}, // node 5
}
fmt.Print("BFS from node 0: ")
bfs(adj, 0)
fmt.Println()
}
上述代码实现了标准的 BFS 遍历算法。C++ 使用 STL 的 queue 容器;C 语言手动实现了一个基于数组的简单队列;Python 使用 collections.deque 双端队列,popleft() 操作为 O(1);Go 使用切片(Slice)模拟队列,通过 queue[0] 取队首、queue[1:] 弹出队首。所有实现都使用 visited 数组避免重复访问。
运行该程序将输出:
BFS from node 0: 0 1 2 3 4 5
BFS 求最短路径(无权图)
在无权图(Unweighted Graph)中,BFS 天然地能找到从起点到其他所有可达节点的最短路径。这是因为 BFS 按层次扩展——先到达的节点,经过的边数一定最少。我们只需额外维护两个数组:
dist[i]:从起点到节点i的最短距离(经过的边数)parent[i]:在最短路径上,节点i的前驱节点(用于回溯路径)
当首次访问到某个节点时,它的距离就是当前层的距离加 1,同时记录前驱节点。
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
const int INF = -1; // Use -1 to represent unreachable
// BFS shortest path from source to all nodes
void bfsShortestPath(const vector<vector<int>>& adj, int source) {
int n = adj.size();
vector<int> dist(n, INF);
vector<int> parent(n, -1);
queue<int> q;
// Initialize source
dist[source] = 0;
q.push(source);
while (!q.empty()) {
int node = q.front();
q.pop();
for (int neighbor : adj[node]) {
if (dist[neighbor] == INF) {
// First time reaching this node = shortest path
dist[neighbor] = dist[node] + 1;
parent[neighbor] = node;
q.push(neighbor);
}
}
}
// Print shortest distances
cout << "Shortest distance from " << source << ":" << endl;
for (int i = 0; i < n; i++) {
cout << " to " << i << ": " << dist[i] << endl;
}
// Reconstruct path from source to target
int target = 5;
cout << "\nPath from " << source << " to " << target << ": ";
if (dist[target] == INF) {
cout << "unreachable" << endl;
} else {
vector<int> path;
for (int cur = target; cur != -1; cur = parent[cur]) {
path.push_back(cur);
}
// Reverse to get source -> target order
for (int i = path.size() - 1; i >= 0; i--) {
cout << path[i];
if (i > 0) cout << " -> ";
}
cout << " (distance: " << dist[target] << ")" << endl;
}
}
int main() {
vector<vector<int>> adj = {
{1, 2}, // node 0
{0, 3}, // node 1
{0, 3}, // node 2
{1, 2, 4, 5}, // node 3
{3, 5}, // node 4
{3, 4}, // node 5
};
bfsShortestPath(adj, 0);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_NODES 6
#define MAX_NEIGHBORS 4
#define INF -1
typedef struct {
int data[MAX_NODES];
int front, rear;
} Queue;
void queueInit(Queue* q) { q->front = 0; q->rear = 0; }
bool queueEmpty(Queue* q) { return q->front == q->rear; }
void queuePush(Queue* q, int val) { q->data[q->rear++] = val; }
int queuePop(Queue* q) { return q->data[q->front++]; }
void bfsShortestPath(int adj[][MAX_NEIGHBORS], int adjCount[], int source, int n) {
int dist[MAX_NODES];
int parent[MAX_NODES];
Queue q;
queueInit(&q);
// Initialize all distances as unreachable
for (int i = 0; i < n; i++) {
dist[i] = INF;
parent[i] = -1;
}
dist[source] = 0;
queuePush(&q, source);
while (!queueEmpty(&q)) {
int node = queuePop(&q);
for (int i = 0; i < adjCount[node]; i++) {
int neighbor = adj[node][i];
if (dist[neighbor] == INF) {
dist[neighbor] = dist[node] + 1;
parent[neighbor] = node;
queuePush(&q, neighbor);
}
}
}
// Print shortest distances
printf("Shortest distance from %d:\n", source);
for (int i = 0; i < n; i++) {
printf(" to %d: %d\n", i, dist[i]);
}
// Reconstruct path from source to target
int target = 5;
printf("\nPath from %d to %d: ", source, target);
if (dist[target] == INF) {
printf("unreachable\n");
} else {
int path[MAX_NODES];
int pathLen = 0;
for (int cur = target; cur != -1; cur = parent[cur]) {
path[pathLen++] = cur;
}
// Print in reverse (source -> target)
for (int i = pathLen - 1; i >= 0; i--) {
printf("%d", path[i]);
if (i > 0) printf(" -> ");
}
printf(" (distance: %d)\n", dist[target]);
}
}
int main() {
int adj[MAX_NODES][MAX_NEIGHBORS] = {0};
int adjCount[MAX_NODES] = {0};
#define ADD_EDGE(u, v) do { \
adj[u][adjCount[u]++] = v; \
adj[v][adjCount[v]++] = u; \
} while(0)
ADD_EDGE(0, 1);
ADD_EDGE(0, 2);
ADD_EDGE(1, 3);
ADD_EDGE(2, 3);
ADD_EDGE(3, 4);
ADD_EDGE(3, 5);
ADD_EDGE(4, 5);
bfsShortestPath(adj, adjCount, 0, MAX_NODES);
return 0;
}
from collections import deque
def bfs_shortest_path(adj, source):
"""BFS shortest path from source to all nodes in unweighted graph."""
n = len(adj)
dist = [-1] * n # -1 means unreachable
parent = [-1] * n
q = deque()
dist[source] = 0
q.append(source)
while q:
node = q.popleft()
for neighbor in adj[node]:
if dist[neighbor] == -1:
# First time reaching this node = shortest path
dist[neighbor] = dist[node] + 1
parent[neighbor] = node
q.append(neighbor)
# Print shortest distances
print(f"Shortest distance from {source}:")
for i in range(n):
print(f" to {i}: {dist[i]}")
# Reconstruct path from source to target
target = 5
print(f"\nPath from {source} to {target}: ", end="")
if dist[target] == -1:
print("unreachable")
else:
path = []
cur = target
while cur != -1:
path.append(cur)
cur = parent[cur]
path.reverse()
print(" -> ".join(map(str, path)), end="")
print(f" (distance: {dist[target]})")
if __name__ == "__main__":
adj = {
0: [1, 2],
1: [0, 3],
2: [0, 3],
3: [1, 2, 4, 5],
4: [3, 5],
5: [3, 4],
}
bfs_shortest_path(adj, 0)
package main
import "fmt"
func bfsShortestPath(adj [][]int, source int) {
n := len(adj)
dist := make([]int, n)
parent := make([]int, n)
// Initialize: -1 means unreachable, -1 for parent means no predecessor
for i := range dist {
dist[i] = -1
parent[i] = -1
}
dist[source] = 0
queue := []int{source}
for len(queue) > 0 {
node := queue[0]
queue = queue[1:]
for _, neighbor := range adj[node] {
if dist[neighbor] == -1 {
// First time reaching this node = shortest path
dist[neighbor] = dist[node] + 1
parent[neighbor] = node
queue = append(queue, neighbor)
}
}
}
// Print shortest distances
fmt.Printf("Shortest distance from %d:\n", source)
for i := 0; i < n; i++ {
fmt.Printf(" to %d: %d\n", i, dist[i])
}
// Reconstruct path from source to target
target := 5
fmt.Printf("\nPath from %d to %d: ", source, target)
if dist[target] == -1 {
fmt.Println("unreachable")
} else {
var path []int
for cur := target; cur != -1; cur = parent[cur] {
path = append(path, cur)
}
// Reverse to get source -> target order
for i, j := 0, len(path)-1; i < j; i, j = i+1, j-1 {
path[i], path[j] = path[j], path[i]
}
for i, v := range path {
if i > 0 {
fmt.Print(" -> ")
}
fmt.Print(v)
}
fmt.Printf(" (distance: %d)\n", dist[target])
}
}
func main() {
adj := [][]int{
{1, 2}, // node 0
{0, 3}, // node 1
{0, 3}, // node 2
{1, 2, 4, 5}, // node 3
{3, 5}, // node 4
{3, 4}, // node 5
}
bfsShortestPath(adj, 0)
}
上述代码在标准 BFS 的基础上增加了 dist 和 parent 两个数组。dist[i] 记录从起点到节点 i 的最短距离(边数),parent[i] 记录在最短路径树中的前驱节点。当首次发现某个节点时(dist[neighbor] == -1),它的最短距离就确定了。回溯路径时从目标节点沿着 parent 链回到起点,再反转即可得到从起点到目标的完整路径。
在示例图中,从节点 0 到节点 5 有两条最短路径:0 -> 1 -> 3 -> 5(距离 3)和 0 -> 2 -> 3 -> 5(距离 3)。BFS 只会找到其中一条,具体取决于邻接表中邻居的存储顺序。
运行该程序将输出:
Shortest distance from 0:
to 0: 0
to 1: 1
to 2: 1
to 3: 2
to 4: 3
to 5: 3
Path from 0 to 5: 0 -> 1 -> 3 -> 5 (distance: 3)
BFS 检测连通性
BFS 可以用来检测图的连通性(Connectivity)。从一个节点出发执行 BFS,统计被访问到的节点数量。如果访问到的节点数等于图中的总节点数,则说明该图是连通图(Connected Graph);否则图不连通,未被访问的节点属于其他连通分量(Connected Component)。
要对整个图找出所有连通分量,只需对每个未访问的节点都执行一次 BFS,每次 BFS 访问到的节点构成一个连通分量。
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
// Check if the graph is connected using BFS
bool isConnected(const vector<vector<int>>& adj) {
int n = adj.size();
if (n == 0) return true;
vector<bool> visited(n, false);
queue<int> q;
// Start BFS from node 0
q.push(0);
visited[0] = true;
int count = 1;
while (!q.empty()) {
int node = q.front();
q.pop();
for (int neighbor : adj[node]) {
if (!visited[neighbor]) {
visited[neighbor] = true;
count++;
q.push(neighbor);
}
}
}
return count == n;
}
// Find all connected components
void findComponents(const vector<vector<int>>& adj) {
int n = adj.size();
vector<bool> visited(n, false);
int componentId = 0;
for (int i = 0; i < n; i++) {
if (!visited[i]) {
componentId++;
vector<int> component;
// BFS from unvisited node i
queue<int> q;
q.push(i);
visited[i] = true;
while (!q.empty()) {
int node = q.front();
q.pop();
component.push_back(node);
for (int neighbor : adj[node]) {
if (!visited[neighbor]) {
visited[neighbor] = true;
q.push(neighbor);
}
}
}
cout << "Component " << componentId << ": ";
for (int v : component) {
cout << v << " ";
}
cout << endl;
}
}
}
int main() {
// Connected graph
vector<vector<int>> adj1 = {
{1, 2}, // node 0
{0, 3}, // node 1
{0, 3}, // node 2
{1, 2, 4, 5}, // node 3
{3, 5}, // node 4
{3, 4}, // node 5
};
cout << "Graph 1 is " << (isConnected(adj1) ? "connected" : "not connected") << endl;
findComponents(adj1);
// Disconnected graph: {0,1} and {2,3,4,5} are separate
vector<vector<int>> adj2 = {
{1}, // node 0
{0}, // node 1
{3, 4}, // node 2
{2, 4}, // node 3
{2, 3}, // node 4
{}, // node 5 (isolated)
};
cout << "\nGraph 2 is " << (isConnected(adj2) ? "connected" : "not connected") << endl;
findComponents(adj2);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#define MAX_NODES 6
#define MAX_NEIGHBORS 4
typedef struct {
int data[MAX_NODES];
int front, rear;
} Queue;
void queueInit(Queue* q) { q->front = 0; q->rear = 0; }
bool queueEmpty(Queue* q) { return q->front == q->rear; }
void queuePush(Queue* q, int val) { q->data[q->rear++] = val; }
int queuePop(Queue* q) { return q->data[q->front++]; }
bool isConnected(int adj[][MAX_NEIGHBORS], int adjCount[], int n) {
if (n == 0) return true;
bool visited[MAX_NODES] = {false};
Queue q;
queueInit(&q);
queuePush(&q, 0);
visited[0] = true;
int count = 1;
while (!queueEmpty(&q)) {
int node = queuePop(&q);
for (int i = 0; i < adjCount[node]; i++) {
int neighbor = adj[node][i];
if (!visited[neighbor]) {
visited[neighbor] = true;
count++;
queuePush(&q, neighbor);
}
}
}
return count == n;
}
void findComponents(int adj[][MAX_NEIGHBORS], int adjCount[], int n) {
bool visited[MAX_NODES] = {false};
int componentId = 0;
for (int i = 0; i < n; i++) {
if (!visited[i]) {
componentId++;
int component[MAX_NODES];
int compLen = 0;
Queue q;
queueInit(&q);
queuePush(&q, i);
visited[i] = true;
while (!queueEmpty(&q)) {
int node = queuePop(&q);
component[compLen++] = node;
for (int j = 0; j < adjCount[node]; j++) {
int neighbor = adj[node][j];
if (!visited[neighbor]) {
visited[neighbor] = true;
queuePush(&q, neighbor);
}
}
}
printf("Component %d: ", componentId);
for (int k = 0; k < compLen; k++) {
printf("%d ", component[k]);
}
printf("\n");
}
}
}
int main() {
// Connected graph (graph 1)
int adj1[MAX_NODES][MAX_NEIGHBORS] = {0};
int adjCount1[MAX_NODES] = {0};
#define ADD_EDGE1(u, v) do { \
adj1[u][adjCount1[u]++] = v; \
adj1[v][adjCount1[v]++] = u; \
} while(0)
ADD_EDGE1(0, 1);
ADD_EDGE1(0, 2);
ADD_EDGE1(1, 3);
ADD_EDGE1(2, 3);
ADD_EDGE1(3, 4);
ADD_EDGE1(3, 5);
ADD_EDGE1(4, 5);
printf("Graph 1 is %s\n", isConnected(adj1, adjCount1, MAX_NODES) ? "connected" : "not connected");
findComponents(adj1, adjCount1, MAX_NODES);
// Disconnected graph (graph 2): {0,1}, {2,3,4}, {5}
int adj2[MAX_NODES][MAX_NEIGHBORS] = {0};
int adjCount2[MAX_NODES] = {0};
#define ADD_EDGE2(u, v) do { \
adj2[u][adjCount2[u]++] = v; \
adj2[v][adjCount2[v]++] = u; \
} while(0)
ADD_EDGE2(0, 1);
ADD_EDGE2(2, 3);
ADD_EDGE2(2, 4);
ADD_EDGE2(3, 4);
printf("\nGraph 2 is %s\n", isConnected(adj2, adjCount2, MAX_NODES) ? "connected" : "not connected");
findComponents(adj2, adjCount2, MAX_NODES);
return 0;
}
from collections import deque
def is_connected(adj):
"""Check if the graph is connected using BFS."""
n = len(adj)
if n == 0:
return True
visited = [False] * n
q = deque()
# Start BFS from node 0
q.append(0)
visited[0] = True
count = 1
while q:
node = q.popleft()
for neighbor in adj[node]:
if not visited[neighbor]:
visited[neighbor] = True
count += 1
q.append(neighbor)
return count == n
def find_components(adj):
"""Find all connected components using BFS."""
n = len(adj)
visited = [False] * n
component_id = 0
for i in range(n):
if not visited[i]:
component_id += 1
component = []
# BFS from unvisited node i
q = deque()
q.append(i)
visited[i] = True
while q:
node = q.popleft()
component.append(node)
for neighbor in adj[node]:
if not visited[neighbor]:
visited[neighbor] = True
q.append(neighbor)
print(f"Component {component_id}: {' '.join(map(str, component))}")
if __name__ == "__main__":
# Connected graph
adj1 = {
0: [1, 2],
1: [0, 3],
2: [0, 3],
3: [1, 2, 4, 5],
4: [3, 5],
5: [3, 4],
}
print(f"Graph 1 is {'connected' if is_connected(adj1) else 'not connected'}")
find_components(adj1)
# Disconnected graph: {0,1}, {2,3,4}, {5}
adj2 = {
0: [1],
1: [0],
2: [3, 4],
3: [2, 4],
4: [2, 3],
5: [],
}
print(f"\nGraph 2 is {'connected' if is_connected(adj2) else 'not connected'}")
find_components(adj2)
package main
import "fmt"
// Check if the graph is connected using BFS
func isConnected(adj [][]int) bool {
n := len(adj)
if n == 0 {
return true
}
visited := make([]bool, n)
queue := []int{0}
visited[0] = true
count := 1
for len(queue) > 0 {
node := queue[0]
queue = queue[1:]
for _, neighbor := range adj[node] {
if !visited[neighbor] {
visited[neighbor] = true
count++
queue = append(queue, neighbor)
}
}
}
return count == n
}
// Find all connected components
func findComponents(adj [][]int) {
n := len(adj)
visited := make([]bool, n)
componentId := 0
for i := 0; i < n; i++ {
if !visited[i] {
componentId++
var component []int
// BFS from unvisited node i
queue := []int{i}
visited[i] = true
for len(queue) > 0 {
node := queue[0]
queue = queue[1:]
component = append(component, node)
for _, neighbor := range adj[node] {
if !visited[neighbor] {
visited[neighbor] = true
queue = append(queue, neighbor)
}
}
}
fmt.Printf("Component %d: %v\n", componentId, component)
}
}
}
func main() {
// Connected graph
adj1 := [][]int{
{1, 2}, // node 0
{0, 3}, // node 1
{0, 3}, // node 2
{1, 2, 4, 5}, // node 3
{3, 5}, // node 4
{3, 4}, // node 5
}
connected := "connected"
if !isConnected(adj1) {
connected = "not connected"
}
fmt.Println("Graph 1 is", connected)
findComponents(adj1)
// Disconnected graph: {0,1}, {2,3,4}, {5}
adj2 := [][]int{
{1}, // node 0
{0}, // node 1
{3, 4}, // node 2
{2, 4}, // node 3
{2, 3}, // node 4
{}, // node 5 (isolated)
}
connected = "connected"
if !isConnected(adj2) {
connected = "not connected"
}
fmt.Println("\nGraph 2 is", connected)
findComponents(adj2)
}
上述代码实现了两种功能:isConnected 函数从节点 0 出发执行 BFS,统计访问到的节点数,与总节点数比较来判断连通性;findComponents 函数对所有未访问的节点逐一启动 BFS,每次 BFS 找到的节点集就是一个连通分量。
测试使用了两个图:第一个是本文的示例图(连通的,只有一个连通分量);第二个是断开的图,其中 {0,1} 是一个连通分量,{2,3,4} 是另一个连通分量,{5} 是孤立节点(Isolated Node)。
运行该程序将输出:
Graph 1 is connected
Component 1: 0 1 2 3 4 5
Graph 2 is not connected
Component 1: 0 1
Component 2: 2 3 4
Component 3: 5
BFS 的性质
下表总结了 BFS 算法的时间和空间复杂度:
| 指标 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度(Time Complexity) | O(V + E) | 每个顶点最多入队一次(O(V)),每条边最多检查两次(O(E)) |
| 空间复杂度(Space Complexity) | O(V) | 队列最多存储 V 个节点,visited 数组大小为 V |
其中 V 是顶点数(Vertex Count),E 是边数(Edge Count)。
BFS 的关键性质:
- 最短路径保证:在无权图中,BFS 保证首次到达某节点时经过的路径就是最短路径。这是 BFS 最核心的应用价值。
- 层次遍历:BFS 按距起点的距离逐层扩展,天然实现了层次遍历(Level-order Traversal)。
- 完备性:BFS 是完备的搜索策略——如果目标存在,BFS 一定能找到它。
- 不含权重:对于带权图,BFS 不能保证最短路径,需要使用 Dijkstra 算法(Dijkstra's Algorithm)等。
BFS 的典型应用场景:
| 应用场景 | 说明 |
|---|---|
| 最短路径(无权图) | 找到边数最少的路径 |
| 层次遍历 | 二叉树的按层遍历就是 BFS |
| 社交网络(Social Network) | 计算两个人之间的分离度(Degrees of Separation) |
| 网络爬虫(Web Crawler) | 从种子 URL 开始逐层爬取网页 |
| 走迷宫(Maze Solving) | 找到从入口到出口的最短路径 |
| 连通分量检测 | 判断图是否连通,或找出所有连通分量 |
| 二分图检测(Bipartite Check) | 通过 BFS 染色判断图是否为二分图 |

浙公网安备 33010602011771号