数据结构——广度优先搜索
要实现图的“分层遍历”,广度优先搜索(BFS)是核心方法——它遵循“先访问离起始顶点近的顶点,再逐步向外扩散”的逻辑,像水波纹一样覆盖所有可达顶点。下面从思想、过程、实现和特点展开,结合图示详细讲解。
广度优先搜索
图的遍历需要按规则访问所有顶点,广度优先搜索(BFS)的核心是“分层探索、逐层扩散”,是探索图结构的另一种基础方法。
1. 广度优先搜索的基本思想
广度优先搜索的逻辑可以类比“洪水蔓延”:从起始顶点(洪水源头)出发,先淹没与源头直接相邻的所有顶点(第一层);再从这些顶点出发,淹没它们的邻接顶点(第二层);以此类推,直到所有可达顶点都被“淹没”(访问)。具体到图中,就是从起始顶点出发,先访问该顶点,再依次访问它的所有未访问邻接顶点;然后对这些邻接顶点,再依次访问它们的未访问邻接顶点,直到所有顶点都被访问。
2. BFS的遍历过程(结合图示分析)
以提供的广度优先搜索过程图为例,假设从顶点V1V_1V1开始遍历,整个过程可拆解为“分层访问”的阶段:
- 第0层(起始层):访问V1V_1V1(标记为已访问),将其加入队列,作为遍历的起点。
- 第1层(直接邻接层):从队列中取出V1V_1V1,访问其所有邻接顶点V2V_2V2和V3V_3V3(标记为已访问),并将V2V_2V2、V3V_3V3加入队列。
- 第2层(次邻接层):从队列中取出V2V_2V2,访问其所有未访问邻接顶点V4V_4V4、V5V_5V5(标记为已访问),并将V4V_4V4、V5V_5V5加入队列;接着取出V3V_3V3,访问其所有未访问邻接顶点V6V_6V6、V7V_7V7(标记为已访问),并将V6V_6V6、V7V_7V7加入队列。
- 第3层(更外层):从队列中取出V4V_4V4,访问其未访问邻接顶点V8V_8V8(标记为已访问),并将V8V_8V8加入队列;取出V5V_5V5,访问其邻接顶点(若有未访问的);取出V6V_6V6,访问其邻接顶点;取出V7V_7V7,访问其邻接顶点。
- 第4层及后续:从队列中取出V8V_8V8,访问其邻接顶点V4V_4V4(已访问)、V5V_5V5(已访问)等,直到队列空,遍历结束。
图中顶点的层级关系清晰体现了“逐层扩散”的特点:V1V_1V1是第0层,V2、V3V_2、V_3V2、V3是第1层,V4、V5、V6、V7V_4、V_5、V_6、V_7V4、V5、V6、V7是第2层,V8V_8V8是第3层,严格遵循“先访问近的顶点,再访问远的顶点”的逻辑。
3. BFS的实现方式
BFS的实现依赖队列(先进先出的特性,保证“分层访问”的顺序),主要有“队列模拟”的非递归实现(递归实现不符合BFS的分层逻辑,一般不采用)。
(1)队列模拟的实现
以邻接表存储的图为例,代码框架如下(类C语言):
// 假设图的邻接表结构为:顶点数组+弧结点,visited数组标记顶点是否已访问
typedef struct {VNode vertices[MAX_VERTEX_NUM]; // 顶点数组int vexnum, arcnum; // 顶点数和弧数
} Graph;void BFS(Graph G, int start, bool visited[]) {Queue Q;InitQueue(&Q); // 初始化队列visited[start] = true; // 标记起始顶点为已访问EnQueue(&Q, start); // 起始顶点入队printf("访问顶点:%d\n", start); // 访问顶点(可替换为业务操作)while (!QueueEmpty(Q)) {int v = DeQueue(&Q); // 取出队首顶点// 遍历v的所有邻接顶点,将未访问的入队并标记for (ArcNode *p = G.vertices[v].firstarc; p != NULL; p = p->nextarc) {int w = p->adjvex;if (!visited[w]) {visited[w] = true;EnQueue(&Q, w);printf("访问顶点:%d\n", w); // 访问邻接顶点}}}
}// 调用入口:初始化visited数组,处理非连通图(多个连通分量)
void BFSTraverse(Graph G) {bool visited[MAX_VERTEX_NUM] = {false}; // 初始化所有顶点为未访问for (int i = 0; i < G.vexnum; i++) {if (!visited[i]) {BFS(G, i, visited); // 对每个未访问的顶点,启动BFS}}
}
队列实现的核心是“先入队的顶点先处理,其邻接顶点后入队”,严格保证了“分层遍历”的顺序——同一层的顶点会在队列中集中处理,体现了BFS“广度优先”的特性。
4. BFS的复杂度与特点
-
时间复杂度:
- 若图用邻接表存储,每个顶点和每条边都被访问一次,时间复杂度为O(n+e)O(n + e)O(n+e)(nnn为顶点数,eee为边数);
- 若图用邻接矩阵存储,需遍历每个顶点的所有邻接顶点,时间复杂度为O(n2)O(n^2)O(n2)。
-
空间复杂度:
主要消耗在队列上,最坏情况下(如完全图,起始顶点的邻接顶点数为n−1n-1n−1)队列需存储n−1n-1n−1个顶点,空间复杂度为O(n)O(n)O(n)。 -
核心特点:
- 能高效解决“无权图的最短路径”问题(因为是分层遍历,第一次访问某顶点时的路径就是最短路径);
- 遍历路径不唯一(邻接顶点的选择顺序不同,遍历序列不同),但都遵循“分层扩散”的核心逻辑;
- 仅能通过队列实现(递归无法体现分层逻辑),代码逻辑清晰,适合处理“按层探索”的场景。
综上,广度优先搜索通过“队列+分层扩散”的逻辑,实现了图的遍历,能高效解决无权图的最短路径等问题。结合图示的遍历过程,能直观理解“逐层访问、向外扩散”的执行逻辑,是图论中探索层次、最短路径的基础算法,为后续图的应用(如拓扑排序、最短路径算法)提供了遍历支撑。