Skip to content

广度优先搜索(BFS)

为什么需要广度优先搜索

图的遍历是图论算法的基础。与树不同,图中可能存在回路,直接遍历会导致死循环,因此需要专门的遍历策略。

广度优先搜索(Breadth-First Search, BFS)是图的两种基本遍历方式之一。它从某个顶点出发,逐层向外扩展访问所有可达顶点,类似于树的层序遍历。BFS 在求解无权图最短路径、判断连通性等问题中有直接应用,是 408 考研图论章节的核心考点。

核心思想

BFS 的核心特点:

  • 逐层访问:先访问起始顶点,再访问其所有邻接顶点,然后访问邻接顶点的邻接顶点,依此类推
  • 借助队列:用队列(FIFO)保存已访问但邻接顶点尚未全部访问的顶点,保证层次顺序
  • visited 数组:用布尔数组标记每个顶点是否已被访问,防止重复入队

BFS 的过程可以类比"水波扩散"——从一个点投下石子,波纹一圈一圈向外扩展。

层次 0:  v₀
层次 1:  v₁  v₂  v₃      (v₀ 的邻接顶点)
层次 2:  v₄  v₅           (v₁v₂v₃ 的未访问邻接顶点)
...

交互可视化

通过下方的交互动画,你可以逐步观察广度优先搜索的执行过程:

加载可视化中...

操作详解

算法思路

从顶点 v 出发进行 BFS 的步骤:

  1. 访问起始顶点 v,标记 visited[v] = true,将 v 入队
  2. 当队列非空时,循环执行:
    • 队头顶点 u 出队
    • 依次检查 u 的所有邻接顶点 w:若 visited[w] == false,则访问 w,标记 visited[w] = true,将 w 入队
  3. 队列为空时,从 v 出发可达的所有顶点均已访问

对于非连通图,一次 BFS 只能访问一个连通分量。需要遍历所有顶点,对未访问的顶点再次调用 BFS,才能完成整个图的遍历。

邻接矩阵实现

c
#define MAX_VERTEX 100

bool visited[MAX_VERTEX];
int Graph[MAX_VERTEX][MAX_VERTEX]; // 邻接矩阵
int n; // 顶点数

void BFS(int v) {
    int queue[MAX_VERTEX], front = 0, rear = 0;
    printf("%d ", v);        // 访问起始顶点
    visited[v] = true;
    queue[rear++] = v;       // 入队
    while (front != rear) {
        int u = queue[front++]; // 出队
        for (int w = 0; w < n; w++) {
            if (Graph[u][w] == 1 && !visited[w]) {
                printf("%d ", w);
                visited[w] = true;
                queue[rear++] = w;
            }
        }
    }
}

// 遍历整个图(处理非连通图)
void BFSTraverse() {
    for (int i = 0; i < n; i++)
        visited[i] = false;
    for (int i = 0; i < n; i++) {
        if (!visited[i])
            BFS(i);
    }
}

邻接矩阵中,查找顶点 u 的所有邻接顶点需要遍历矩阵的一整行,因此内层循环执行 O(V) 次。

邻接表实现

c
typedef struct ArcNode {
    int adjvex;              // 邻接顶点编号
    struct ArcNode *next;
} ArcNode;

typedef struct {
    int data;                // 顶点数据
    ArcNode *first;          // 边链表头指针
} VNode;

VNode AdjList[MAX_VERTEX];

void BFS_AdjList(int v) {
    int queue[MAX_VERTEX], front = 0, rear = 0;
    printf("%d ", v);
    visited[v] = true;
    queue[rear++] = v;
    while (front != rear) {
        int u = queue[front++];
        ArcNode *p = AdjList[u].first;
        while (p != NULL) {
            int w = p->adjvex;
            if (!visited[w]) {
                printf("%d ", w);
                visited[w] = true;
                queue[rear++] = w;
            }
            p = p->next;
        }
    }
}

邻接表中,每个顶点只遍历其边链表,总共访问所有边结点恰好一次(无向图为 2E 次),效率更高。

BFS 生成树

对连通图从顶点 v 进行 BFS 时,所有引起顶点首次被访问的边构成一棵树,称为 BFS 生成树

BFS 生成树的特点:

  • 树的根为起始顶点 v
  • 树中从根到任意顶点的路径,就是原图中从 v 到该顶点的最短路径(边数最少)
  • 同一张图,邻接矩阵和邻接表的存储方式不同(邻接顶点的遍历顺序不同),生成树可能不同

对于非连通图,每个连通分量各产生一棵 BFS 生成树,合在一起构成 BFS 生成森林

复杂度分析

存储方式时间复杂度说明
邻接表O(V + E)每个顶点入队一次 O(V),每条边检查一次 O(E)
邻接矩阵O(V²)每个顶点入队一次,查找邻接顶点需遍历一整行

空间复杂度:O(V),主要开销为 visited 数组(O(V))和辅助队列(最坏情况下存储 O(V) 个顶点)。

考研高频考点

  • ⭐ 给定图结构,写出 BFS 遍历序列(选择题/填空题高频,注意起始顶点和存储方式影响序列)
  • ⭐ BFS 与 DFS 的对比(简答题必考):BFS 用队列、DFS 用栈(递归);BFS 适合求最短路径、DFS 适合求路径是否存在
  • ⭐ BFS 的时间复杂度:邻接表 O(V+E),邻接矩阵 O(V²)(选择题常考)
  • ⭐ BFS 生成树/森林的概念及性质(树的路径 = 最短路径)
  • BFS 判断图的连通性:调用 BFS 的次数 = 连通分量个数
  • BFS 求无权图单源最短路径(应用题)