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 的核心算法步骤如下:

  1. 创建一个队列(Queue),将起始节点入队
  2. 创建一个访问标记数组(Visited Array),标记起始节点为已访问
  3. 当队列不为空时,执行以下操作:
    • 从队列头部取出一个节点(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 的基础上增加了 distparent 两个数组。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 染色判断图是否为二分图
posted @ 2026-04-17 07:54  游翔  阅读(19)  评论(0)    收藏  举报