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

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

  • 核心特点

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

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

相关推荐
闪电悠米3 小时前
力扣hot100-41.缺失的第一个正数-原地哈希详解
数据结构·算法·哈希算法
WL学习笔记3 小时前
堆(数据结构)
数据结构·
wabs6667 小时前
关于图论【卡码网100.最大岛屿的面积的思考】
数据结构·算法·图论
yuannl109 小时前
图的最短路径Dijkstra
数据结构
zander25810 小时前
138. 随机链表的复制
数据结构·算法·链表
过期动态10 小时前
【LeetCode 热题 100】找到字符串中所有字母异位词
java·数据结构·算法·leetcode·职场和发展·rabbitmq
来一碗刘肉面10 小时前
栈在递归中的应用
数据结构·算法
皓月斯语10 小时前
程序设计语言的特点
开发语言·数据结构·c++
邪修king11 小时前
C++ 进阶终章:异常机制与智能指针全解 —— 从错误处理到 RAII 资源管理,打通现代 C++ 的核心命脉
android·数据结构·c++
梓䈑16 小时前
【算法题攻略】BFS 解决FloodFill 算法、最短路问题、多源BFS 和 拓扑排序
c++·算法·宽度优先