江小南

V1

2023/03/12阅读:21主题:萌绿

【数据结构】图的遍历—广度优先遍历、深度优先遍历

1. 图的广度优先遍历

1. 回顾二叉树的层序遍历

【数据结构】二叉树的先中后序遍历与层序遍历

层序遍历也叫层次遍历,也叫广度优先遍历。

算法思想:

  1. 初始化一个辅助队列。
  2. 根结点入队。
  3. 若队列非空,则队头结点出队,访问该节点。并将其左、右孩子插入队尾(如果有的话)。
  4. 重复3的步骤直至队列为空。

2. 图的广度优先遍历

要点:

  1. 找到与一个顶点相邻的所有顶点。
  2. 标记哪些顶点被访问过。
  3. 需要一个辅助队列。

FirstNeighbor(G,x):求图G中顶点x的第一个邻接点,若有则返回顶点号。若x没有邻接点或图中不存在x,则返回-1。

NextNeighbor(G,x,y):假设图G中顶点y是顶点x的一个邻接点,返回除y之外顶点x的下一个邻接点的顶点号,若y是x的最后一个邻接点,则返回-1。

bool visited[MAX_VERTEX_NUM];  // 访问标记数组,初始都为false

// 广度优先遍历
void BFS(){
    visit(v);  // 访问初始顶点v
    visited[v]=true;  // 对v做已访问标记
    Enqueue(Q,v);  // 顶点v入队列Q
    while(!isEmpty(Q)){
        DeQueue(Q,v);  // 顶点v出队列
        for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w)){
            // 检测v所有邻接点
            if(!visited[w]){
                visit(w);  // 访问顶点w
                visited[w]=true;  // 对w做已访问标记
                EnQueue(Q,w);  // 顶点w入队列
            }
        }
    }
}

// 对图G进行广度优先遍历
void BFSTraverse(Graph G){
    for(i=0;i<G.vexnum;++i){
        visited[i]=false;  // 访问标记数组初始化
    }
    InitQueue(Q);  // 初始化辅助队列Q
    for(i=0;i<G.vexnum;++i){  // 从0号顶点开始遍历
        if(!visited[i]){  // 对每个连通分量调用一次BFS
            BFS(G,i);  // vi未访问过,从vi开始BFS
        }
    }
}

说明:BFSTraverse多加了一层for循环,是为了如果有多个连通分量的情况,可以遍历数组,从false的结点处开始遍历。

3. 广度优先遍历序列

从顶点1出发得到的广度优先遍历序列: 1、2、5、6、3、7、4、8

从顶点3出发得到的广度优先遍历序列: 3、4、6、7、8、2、1、5

注意

  • 同一个图的邻接矩阵表示方式唯一,因此广度优先遍历序列唯一。
  • 同一个图的邻接表表示方式不唯一,因为广度优先遍历序列不唯一。
  • 一般按照序号顺序。

4. 复杂度分析

空间复杂度

最坏情况,一个顶点连接了尽可能多的顶点,辅助队列大小为O(|V|)。

时间复杂度

5. 广度优先生成树、森林

注意:广度优先生成树由广度优先遍历过程确定。由于邻接表的表示方式不唯一,因此基于邻接表的广度优先生成树也不唯一。

说明:对于非连通图的广度优先遍历,可得到广度优先生成森林。

2. 图的深度优先遍历

1. 回顾二叉树的深度优先遍历

【数据结构】二叉树的先中后序遍历与层序遍历

根左右(NLR)

先序遍历也叫深度优先遍历。

先序遍历操作过程如下:

  1. 若二叉树为空,则什么也不做;
  2. 若二叉树非空:
  • 访问根结点;
  • 先序遍历左子树;
  • 先序遍历右子树。

树的深度优先遍历:从根结点出发,能往更深处走就尽量往深处走。每当访问一个结点的时候,要检查是否还有与当前结点相邻的且没有被访问过的结点,如果有的话就往下层走。

图的深度优先遍历类似于树的先根遍历。

2. 图的深度优先遍历

bool visited[MAX_VERTEX_NUM];  // 访问标记数组,初始都为false
// 深度优先遍历图G
void DFS(Graph G,int v){
    visit(v);  // 访问顶点v
    visited[v]=true;  // 设已访问标记
    for(w=FirstNeighbor(G,v);w>=0;w=NextNeighbor(G,v,w){
        if(!visited[w]){  // w为u的尚未访问的邻接结点
            DFS(G,w);
        }
    }
}
// 对图G进行深度优先遍历
void DFSTraverse(Graph G){
    for(v=0;v<G.vexnum;++v){
        visited[v]=false;  // 初始化以访问标记数据
    }
    for(v=0;v<G.vexnum;++v){  // 本代码中是v0开始遍历
        if(!visited[v]){
            DFS(G,v);
        }
    }
}

说明:DFSTraverse多加了一层for循环,是为了如果有多个连通分量的情况,可以遍历数组,从false的结点处开始遍历。

3. 深度优先遍历序列

从2出发的深度优先遍历序列:2、1、5、6、3、4、7、8

从3出发的深度优先遍历序列:3、4、7、6、2、1、5、8

4. 复杂度分析

空间复杂度 来自函数调用栈,最坏情况,递归深度为O(|V|)。

最好情况为O(1)。

时间复杂度

注意

  • 同一个图的邻接矩阵表示方式唯一,因此深度优先遍历序列唯一。
  • 同一个图的邻接表表示方式不唯一,因为深度优先遍历序列不唯一。
  • 一般按照序号顺序。

5. 深度优先生成树、森林

同一个图的邻接矩阵表示方式唯一,因此深度优先遍历序列唯一,深度优先生成树也唯一。

同一个图邻接表表示方式不唯一,因此深度优先遍历序列不唯一,深度优先生成树也不唯一。

深度优先遍历非连通图可得深度优先生成森林。

3. 小结

图的遍历与图的连通性

对于无向图进行BFS/DFS遍历,调用BFS/DFS函数的次数=连通分量数。对于连通图,只需调用1次BFS/DFS。

对于有向图进行BFS/DFS遍历,调用BFS/DFS函数的次数要具体问题具体分析,若起始顶点到其他各顶点都有路径,则只需调用1次BFS/DFS函数。

对于强连通图,从任一结点出发都只要调用1次BFS/DFS。

分类:

后端

标签:

数据结构与算法

作者介绍

江小南
V1