深度优先遍历(DFS)和广度优先遍历(BFS)

1 图的两种存储方式

1.1 邻接矩阵(Adjacency Matrix)

1.1.1 原理

用一维数组存储图中顶点信息;用二维数组(矩阵)存储图中的边和弧的信息。对于无向图来说,如果顶点i与顶点j之间有边,就将A[i][j]和A[j][i]标记为1;对于有向图来说,如果顶点i和顶点j之间,有一条箭头从顶点i指向顶点j的边,就将A[i][j]标记为1,有箭头从顶点j指向顶点i的边,就将A[j][i]标记为1。对于有权图,数组中存储相应权重。

邻接矩阵可表示为:

vertex[4] = {v0, v1, v2, v3};

edge[4][4] = { {0, 1, 1, 0},

{1, 0, 0, 1},

{1, 0, 0, 1},

{0, 1, 1, 0} };

1.1.2 优缺点

1.1.2.1 优点

1)基于数组,存储方式简单、直接,获取顶点关系时非常高效;

2)计算方便,邻接矩阵的方式存储图,可以将很多图的运算转换成矩阵之间的运算。

1.1.2.2 缺点

浪费存储空间。对于无向图来说,如果A[i][j]为1,那么A[j][i]也为1,实际上,我们只需要存储一个就可以了;如果我们存储的是稀疏图(sparse matrix),也就是顶点很多,但每个顶点的边不多,那就更加浪费空间了。

1.1.3 适用场景

1)要求较高速的计算速率;2)运行内存充足;3)图的顶点个数n值较小;4)有向图且为稠密图;

1.1.4 邻接矩阵存储图的代码实现

#ifndef GRAPH_H
#define GRAPH_H

#define MAX_VERTEX (10)

//邻接矩阵图
class AdjacencyMatrixGraph {
private:
    int m_nVertex[MAX_VERTEX];    //顶点数组
    int m_nEdge[MAX_VERTEX][MAX_VERTEX];    //邻接矩阵
    int m_nCurrentVertex;    //当前图中顶点个数
    int m_nCurrentEdge;    //当前图中边的个数
    bool visited[MAX_VERTEX];    //访问数组

public:
    void CreatGraph();    //创建邻接矩阵图
    void DisplayGraph();    //以邻接矩阵形式显示图
    void MatrixDFS();    //深度优先遍历
    void MDFS(int i);    //深度优先遍历递归调用
    void MatrixBFS();    //广度优先遍历
};

#endif
/* 创建邻接矩阵图
 *     1----2
 *   /  \  / | * 6     /\  | 3
 *   \  /   \|/
 *     5---- 4
 * 顶点 6 个、边为 1-2/1-4/1-6/2-3/2-4/2-5/3-4/4-5/5-6共9条
 */
void AdjacencyMatrixGraph::CreatGraph() {
    //输入顶点个数、边数
    std::cout << "输入顶点个数、边数:" << std::endl;
    std::cin >> m_nCurrentVertex >> m_nCurrentEdge;

    //初始化顶点数组,数组值为下表加1
    for (int i = 0; i < m_nCurrentVertex; ++i) {
        m_nVertex[i] = i + 1;
    }
    //初始化邻接矩阵
    for (int i = 0; i < m_nCurrentVertex; ++i) {
        for (int j = 0; j < m_nCurrentVertex; ++j) {
            m_nEdge[i][j] = 0;
        }
    }
    //初始化图
    int m, n;
    for (int i = 0; i < m_nCurrentEdge; ++i) {
        std::cout << "输入顶点m、邻接点n" << std::endl;
        std::cin >> m >> n;
        m_nEdge[m - 1][n - 1] = m_nEdge[n - 1][m - 1] = 1;
    }
}

//以邻接矩阵形式显示图
void AdjacencyMatrixGraph::DisplayGraph() {
    //显示顶点
    std::cout << "顶点有:" << std::endl;
    for (int i = 0; i < m_nCurrentVertex; ++i) {
        std::cout << m_nVertex[i] << " | ";
    }
    std::cout << std::endl;

    /*显示邻接矩阵
     *   1 2 3 4 5 6
     * 1 0 1 0 1 0 1
     * 2 1 0 1 1 1 0
     * 3 0 1 0 1 0 0
     * 4 1 1 1 0 1 0
     * 5 0 1 0 1 0 1
     * 6 1 0 0 0 1 0
     */
    std::cout << "  ";
    for (int i = 0; i < m_nCurrentVertex; ++i) {
        std::cout << m_nVertex[i] << ‘ ‘;
    }
    std::cout << std::endl;
    for (int i = 0; i < m_nCurrentVertex; ++i) {
        std::cout << i + 1 << ‘ ‘;
        for (int j = 0; j < m_nCurrentVertex; ++j) {
            std::cout << m_nEdge[i][j] << ‘ ‘;
        }
        std::cout << std::endl;
    }
}

1.2 邻接表(Adjacency List)

1.2.1 原理

邻接表类似于散列表的拉链表示法。每个顶点对应一条链表,链表中存储的是与这个顶点相连接的其他顶点。以上图为例,邻接表可表示为:

每条链表的头结点组成顶点的数组。

1.2.2 优缺点

1)优点:节省内存空间;2)缺点:邻接表使用起来比较耗时,链表的存储方式堆缓存不友好。

1.2.3 使用场景

1)有内存限制;2)对运行速度要求不高;3)顶点个数较大时;

1.2.4 邻接表存储图的代码实现

#ifndef GRAPH_H
#define GRAPH_H

//邻接表图
typedef struct AdjacencyNode {    //邻接点元素
    int m_nAdjacencyNode;    //邻接点值
    AdjacencyNode* m_pNextNode;
}* AdjacencyNodePtr;

typedef struct VertexNode {    //顶点数组元素
    int m_nVertex;    //顶点值
    AdjacencyNodePtr m_pFirstAdjacencyNode;    //顶点的第一邻接点
}* VertexNodePtr;

class AdjacencyListGraph {
private:
    VertexNodePtr m_pVertexArr;    //指向顶点数组
    int m_nCurrentVertex;
    int m_nCurrentEdge;
    bool* visited;    //指向访问数组

public:
    void CreatGraph();    //创建邻接表图
    void DisplayGraph();    //以邻接表形式显示图
    void ListDFS();    //邻接表的深度优先遍历
    void LDFS(int n);    //深度优先遍历递归函数
    void ListBFS();    //邻接表的广度优先遍历
};

#endif
/* 创建邻接矩阵图
 *     1----2
 *   /  \  / | * 6     /\  | 3
 *   \  /   \|/
 *     5---- 4
 * 顶点 6 个、边为 1-2/1-4/1-6/2-3/2-4/2-5/3-4/4-5/5-6共9条
 */
void AdjacencyListGraph::CreatGraph() {
    std::cout << "输入顶点个数、边数" << std::endl;
    std::cin >> m_nCurrentVertex >> m_nCurrentEdge;
    m_pVertexArr = new VertexNode[m_nCurrentVertex];

    //初始化顶点数组
    for (int i = 0; i < m_nCurrentVertex; ++i) {
        m_pVertexArr[i].m_nVertex = i + 1;
        m_pVertexArr[i].m_pFirstAdjacencyNode = nullptr;
    }

    //初始化邻接表
    for (int i = 0; i < m_nCurrentEdge; ++i) {
        int m, n;
        std::cout << "输入边(m, n)" << std::endl;
        std::cin >> m >> n;

        //顶点m的邻接表
        AdjacencyNodePtr pNewAdjacencyNode = new AdjacencyNode;
        pNewAdjacencyNode->m_pNextNode = m_pVertexArr[m - 1].m_pFirstAdjacencyNode;
        pNewAdjacencyNode->m_nAdjacencyNode = n;
        m_pVertexArr[m - 1].m_pFirstAdjacencyNode = pNewAdjacencyNode;

        //顶点n的邻接表
        pNewAdjacencyNode = new AdjacencyNode;
        pNewAdjacencyNode->m_pNextNode = m_pVertexArr[n - 1].m_pFirstAdjacencyNode;
        pNewAdjacencyNode->m_nAdjacencyNode = m;
        m_pVertexArr[n - 1].m_pFirstAdjacencyNode = pNewAdjacencyNode;
    }
}

//已邻接表显示图
void AdjacencyListGraph::DisplayGraph() {
    for (int i = 0; i < m_nCurrentVertex; ++i) {
        //显示样例:1->2->4->6
        std::cout << m_pVertexArr[i].m_nVertex << "->";
        AdjacencyNodePtr pWorkNode = m_pVertexArr[i].m_pFirstAdjacencyNode;
        while (pWorkNode) {
            std::cout << pWorkNode->m_nAdjacencyNode;
            pWorkNode = pWorkNode->m_pNextNode;
            if (pWorkNode)    std::cout << "->";
        }
        std::cout << std::endl;
    }
}

2 深度优先遍历(DFS)

2.1 原理

深度优先遍历(depth first search)图的方式类似于先序遍历二叉树。从图中的某个顶点出发,访问次顶点,然后从该顶点的未被访问的邻接点出发深度优先遍历,直到图中所有和该顶点相通的点都被访问到。

2.2 实现及关键点

2.2.1 关键点

1)由原理可知,深度优先遍历的实现依赖于递归思想,因此特别注意递归公式和递归终止条件;

2)由于二叉树的层次关系,先序遍历二叉树时是层层递进的,不会重复访问已访问过的节点;但是图并没有层次关系,所以要有标记—访问数组,记录已访问过的顶点,防止顶点的重复访问。

2.2.2 实现

//Adjacency Matrix
//深度优先遍历
void AdjacencyMatrixGraph::MDFS(int i) {
    visited[i] = true;
    std::cout << m_nVertex[i] << " | ";    //打印路径
    for (int j = 0; j < m_nCurrentVertex; ++j) {
        if (!visited[j] && m_nEdge[i][j]) {  //邻接点未被访问且存在边
            MDFS(j);
        }
    }
}
void AdjacencyMatrixGraph::MatrixDFS() {
    //设置访问数组,访问过的顶点设置为true
    for (int i = 0; i < m_nCurrentVertex; ++i) {
        visited[i] = false;
    }

    //从其中一个顶点开始遍历图
    MDFS(0);
    std::cout << std::endl;
}

//Adjacency List
void AdjacencyListGraph::LDFS(int n) {
    visited[n] = true;    //当前遍历顶点置true,表示已访问过
    std::cout << m_pVertexArr[n].m_nVertex << " | ";

    //遍历当前顶点各个邻接点
    AdjacencyNodePtr pWorkNode = m_pVertexArr[n].m_pFirstAdjacencyNode;
    int vertex;
    while (pWorkNode) {
        vertex = pWorkNode->m_nAdjacencyNode;    //邻接点
        //如果顶点还未被访问
        if (!visited[vertex - 1]) {
            LDFS(vertex - 1);
        }
        else {
            pWorkNode = pWorkNode->m_pNextNode;
        }
    }
}
void AdjacencyListGraph::ListDFS() {
    //初始化访问数组
    visited = new bool[m_nCurrentVertex];
    for (int i = 0; i < m_nCurrentVertex; ++i) {
        visited[i] = false;
    }
    //任选一个顶点开始遍历
    LDFS(0);
    std::cout << std::endl;
}

2.3 复杂度分析

2.3.1 邻接矩阵存储方式

1)时间复杂度

遍历每个顶点时,都要判断其余顶点是否与该顶点相邻,所以要遍历整个邻接矩阵中的所有元素,故时间复杂度为O(V2),V为顶点个数。

2)空间复杂度

遍历过程中需要访问数组来标记顶点是否被访问,访问数组大小等于顶点个数V,所以空间复杂度为O(V)。

2.3.2 邻接表存储方式

1)时间复杂度

邻接表的存储方式会先访问顶点,然后再判断链表中的每个邻接点(边),每条会访问2次,所以时间复杂度为O(V+2E),V为顶点个数、E为边的个数。

2)空间复杂度

同邻接矩阵,空间复杂度为O(V)。

3 广度优先遍历(BFS)

3.1 原理

广度优先遍历(breadth first search)可以类比为二叉树的层序遍历;先查找离起始顶点最近的,然后是次进的,依次往外搜索。

3.2 实现及关键点

3.2.1 关键点

1)借助队列将当前遍历顶点的邻接点存储起来,使得搜索路径有层次;

2)同深度优先搜索中,需要借助访问数组标记已访问顶点;

2)顶点元素入队时要将访问数组相关顶点标记为已访问,出队时执行会造成顶点的重复访问。

//Adjacency Matrix
void AdjacencyMatrixGraph::MatrixBFS() {
    //初始化访问数组
    for (int i = 0; i < m_nCurrentVertex; ++i) {
        visited[i] = false;
    }

    std::queue<int> vertexQueue;    //存放已遍历顶点的邻接点
    int frontVertex = 0;    //队列中首元素
    visited[0] = true;
    vertexQueue.push(0);    //将顶点1放入队列中
    while (!vertexQueue.empty()) {
        frontVertex = vertexQueue.front();
        //打印首元素
        std::cout << frontVertex + 1 << " | ";
        //将邻接点加入队列
        for (int i = 0; i < m_nCurrentVertex; ++i) {
            if (!visited[i] && m_nEdge[frontVertex][i]) {
                visited[i] = true;    //注意!访问数组先置位再将顶点下标放入队列,否则会造成重复
                vertexQueue.push(i);
            }
        }
        //首元素出列
        vertexQueue.pop();
    }
    std::cout << std::endl;
}

//Adjacency List
void AdjacencyListGraph::ListBFS() {
    //初始化访问数组
    visited = new bool[m_nCurrentVertex];
    for (int i = 0; i < m_nCurrentVertex; ++i) {
        visited[i] = false;
    }

    std::queue<int> vertexQueue;    //存放已访问顶点的邻接点
    int vertex = m_pVertexArr[0].m_nVertex;
    vertexQueue.push(vertex);    //将遍历起始点放入队列
    visited[vertex - 1] = true;
    std::cout << vertex << " | ";
    while (!vertexQueue.empty()) {
        vertex = vertexQueue.front();
        vertexQueue.pop();
        AdjacencyNodePtr pWorkNode = m_pVertexArr[vertex - 1].m_pFirstAdjacencyNode;
        while (pWorkNode) {
            vertex = pWorkNode->m_nAdjacencyNode;
            if (!visited[vertex - 1]) {
                vertexQueue.push(vertex);
                visited[vertex - 1] = true;
                std::cout << vertex << " | ";
            }
            pWorkNode = pWorkNode->m_pNextNode;
        }
    }
    std::cout << std::endl;
}

2.4 复杂度分析

2.4.1 邻接矩阵存储方式

1)时间复杂度

遍历过程中,每个顶点出队时,都要判断与该顶点相邻的所有顶点是否已访问,因为遍历整个图,所以所有顶点都要有入队、出队操作,所以时间复杂度为O(V2)。

2)空间复杂度

遍历操作用到访问数组与队列,队列长度不超过顶点个数,所以空间复杂为O(V)。

2.4.2 邻接表存储方式

1)时间复杂度

同样的,每个顶点出队时,都要遍历其邻接点链表(边),所以时间复杂度为O(V+2E)。

2)空间复杂度

同邻接矩阵存储方式,空间复杂度为O(V)。

该篇博客是自己的学习博客,水平有限,如果有哪里理解不对的地方,希望大家可以指正!

原文地址:https://www.cnblogs.com/zpchya/p/10894886.html

时间: 2024-10-01 21:19:12

深度优先遍历(DFS)和广度优先遍历(BFS)的相关文章

图的深度优先遍历(DFS)和广度优先遍历(BFS)算法分析

1. 深度优先遍历 深度优先遍历(Depth First Search)的主要思想是: 1.首先以一个未被访问过的顶点作为起始顶点,沿当前顶点的边走到未访问过的顶点: 2.当没有未访问过的顶点时,则回到上一个顶点,继续试探别的顶点,直至所有的顶点都被访问过. 在此我想用一句话来形容 “不到南墙不回头”. 1.1 无向图的深度优先遍历图解 以下"无向图"为例: 对上无向图进行深度优先遍历,从A开始: 第1步:访问A. 第2步:访问B(A的邻接点). 在第1步访问A之后,接下来应该访问的是

深度优先搜索DFS和广度优先搜索BFS

DFS简介 深度优先搜索,从起点开始按照某个原则一直往深处走,直到找到解,或者走不下去,走不下去则回溯到前一节点选择另一条路径走,直到找到解为止. BFS简介 广度优先搜索,从起点开始先搜索其相邻的节点,由此向外不断扩散,直到找到解为止. 举例解释 从1开始去寻找5 DFS: 原则:优先选择左手边 过程:1-2-3-4-6-4-5 BFS: 队列情况:1 2.5     5.3 5出来则找到 遍历图中所有点 DFS: 原则:优先选择左手边 过程:1-2-3-4-6-4-5 BFS: 队列情况:1

广度优先遍历-BFS、深度优先遍历-DFS

广度优先遍历-BFS 广度优先遍历类似与二叉树的层序遍历算法,它的基本思想是:首先访问起始顶点v,接着由v出发,依次访问v的各个未访问的顶点w1 w2 w3....wn,然后再依次访问w1 w2 w3....wn的所有未被访问的邻接顶点:再从这些访问过的顶点出发,再访问它们所有未被访问过的邻接顶点......依次类推,直到图中的所有点都被访问为止.类似的思想还将应用于Dijkstra单源最短路径算法和Prim最小生成树算法. python实现二叉树的建立以及遍历(递归前序.中序.后序遍历,队栈前

图的遍历——DFS和BFS模板(一般的图)

关于图的遍历,通常有深度优先搜索(DFS)和广度优先搜索(BFS),本文结合一般的图结构(邻接矩阵和邻接表),给出两种遍历算法的模板 1.深度优先搜索(DFS) #include<iostream> #include<unordered_map> #include<queue> #include<cstring> #include<cstdlib> #include<cmath> #include<algorithm> #

算法学习笔记 二叉树和图遍历—深搜 DFS 与广搜 BFS

图的深搜与广搜 马上又要秋招了,赶紧复习下基础知识.这里复习下二叉树.图的深搜与广搜.从图的遍历说起,图的遍历方法有两种:深度优先遍历(Depth First Search), 广度优先遍历(Breadth First Search),其经典应用走迷宫.N皇后.二叉树遍历等.遍历即按某种顺序访问"图"中所有的节点,顺序分为: 深度优先(优先往深处走),用的数据结构是栈, 主要是递归实现: 广度优先(优先走最近的),用的数据结构是队列,主要是迭代实现: 对于深搜,由于递归往往可以方便的利

图的邻接表+深度优先遍历+广度优先遍历

1 /** 2 无向图的邻接表存储 3 深度优先遍历递归 4 广度优先遍历递归+非递归 5 */ 6 #include <stdio.h> 7 #include <string.h> 8 #include <malloc.h> 9 #define N 5 10 #define MAX 50 11 typedef struct A{ 12 int adjvex; 13 struct A* nextArc; 14 }Arc; 15 typedef struct node{

算法学习笔记(六) 二叉树和图遍历—深搜 DFS 与广搜 BFS

图的深搜与广搜 复习下二叉树.图的深搜与广搜. 从图的遍历说起.图的遍历方法有两种:深度优先遍历(Depth First Search), 广度优先遍历(Breadth First Search),其经典应用走迷宫.N皇后.二叉树遍历等.遍历即按某种顺序訪问"图"中全部的节点,顺序分为: 深度优先(优先往深处走),用的数据结构是栈, 主要是递归实现. 广度优先(优先走近期的).用的数据结构是队列.主要是迭代实现. 对于深搜.因为递归往往能够方便的利用系统栈,不须要自己维护栈.所以通常实

树的深度优先与广度优先遍历

简述树的深度优先及广度优先遍历算法,并说明非递归实现. 原题出自百度的笔试: 当时我看到这个题目的时候,已经完全记不得非递归算法该怎么实现了,后来查阅了一下,要用到两个辅助的数据结构: 深度优先遍历--->栈: 广度优先遍历--->队列: 这里以二叉树为例来实现. import java.util.ArrayDeque; public class BinaryTree { static class TreeNode{ int value; TreeNode left; TreeNode rig

转:二叉树的深度优先遍历和广度优先遍历

转自:http://www.blogjava.net/fancydeepin/archive/2013/02/03/395073.html 深度优先搜索算法(Depth First Search),是搜索算法的一种.是沿着树的深度遍历树的节点,尽可能深的搜索树的分支. 当节点v的所有边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点.这一过程一直进行到已发现从源节点可达的所有节点为止. 如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为

浅谈图的广度优先遍历

一.广度优先遍历 上次我们浅谈了图的深度优先遍历,接下来我们使用广度优先搜索来遍历这个图: 这五个顶点被访问的顺序如下图所示: 二.实现过程 广度优先搜索过程如下: 首先以一个未被访问过的顶点作为起始顶点,比如以1号顶点为起点. 将1号顶点放入到队列中,然后将与1号顶点相邻的未访问过的顶点,即2号.3号和5号顶点依次放入到队列中. 接下来再将2号顶点相邻的未访问过的4号顶点放入到队列中. 到此所有顶点都被访问过,遍历结束. 广度优先遍历的主要思想: 首先以一个未被访问过的顶点作为起始顶点,访问其