数据结构——广度优先搜索

要实现图的"分层遍历",广度优先搜索(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语言):

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)。

  • 核心特点

    • 能高效解决"无权图的最短路径"问题(因为是分层遍历,第一次访问某顶点时的路径就是最短路径);
    • 遍历路径不唯一(邻接顶点的选择顺序不同,遍历序列不同),但都遵循"分层扩散"的核心逻辑;
    • 仅能通过队列实现(递归无法体现分层逻辑),代码逻辑清晰,适合处理"按层探索"的场景。

综上,广度优先搜索通过"队列+分层扩散"的逻辑,实现了图的遍历,能高效解决无权图的最短路径等问题。结合图示的遍历过程,能直观理解"逐层访问、向外扩散"的执行逻辑,是图论中探索层次、最短路径的基础算法,为后续图的应用(如拓扑排序、最短路径算法)提供了遍历支撑。

相关推荐
小梁努力敲代码5 小时前
java数据结构--LinkedList与链表
java·数据结构·链表
再睡一夏就好6 小时前
【C++闯关笔记】深究继承
java·数据结构·c++·stl·学习笔记
那我掉的头发算什么6 小时前
【数据结构】反射、枚举、lambda表达式以及补充知识
java·jvm·数据结构·intellij idea
大大大大物~7 小时前
数据结构之HashMap(容器)
java·数据结构·容器
allk557 小时前
List && Map在安卓中的优化
android·数据结构·性能优化·list·map
杨福瑞7 小时前
数据结构:顺序表讲解(1)
c语言·开发语言·数据结构
泡沫冰@9 小时前
数据库(6)
数据结构
晨非辰9 小时前
【数据结构入坑指南】--《层序分明:堆的实现、排序与TOP-K问题一站式攻克(源码实战)》
c语言·开发语言·数据结构·算法·面试
Dfreedom.18 小时前
一文掌握Python四大核心数据结构:变量、结构体、类与枚举
开发语言·数据结构·python·变量·数据类型