图的遍历

简介: 图的遍历

图的遍历


介绍


是从图的某一顶点出发,按照某种搜索方式对图中所有顶点访问一次且仅一次。图的遍历可以解决很多搜索问题,在实际中应用非常广泛。图的遍历根据搜索方式的不同,分为广度优先搜索和深度优先搜索。


一.深度优先遍历


1.1介绍


深度优先搜索(Depth First Search, DFS)是最常见的图搜索方法之一。深度优先搜索沿着一条路径一直走下去,无法行进时,回退到刚刚访问的节点,似“不撞南墙不回头,不到黄河不死心”。深度优先遍历是按照深度优先搜索的方式对图进行遍历。

深度优先遍历秘籍:后被访问的顶点,其邻接点先被访问。

根据深度优先遍历秘籍,后来先服务,可以借助于栈实现。递归本身就是使用栈实现的,因此使用递归方法更方便。


举例


20210226022251111.jpg


20210226022300621.jpg

20210226022319278.jpg


代码实现1—深度优先遍历邻接矩阵

//深度优先遍历  邻接矩阵
#include <iostream>
using namespace std;
#define MaxVnum 100  //顶点数最大值
bool visited[MaxVnum];  //访问标志数组,其初值为"false"
typedef char VexType;  //顶点的数据类型,根据需要定义
typedef int EdgeType;  //边上权值的数据类型,若不带权值的图,则为0或1
typedef struct {
    VexType Vex[MaxVnum];
    EdgeType Edge[MaxVnum][MaxVnum];
    int vexnum, edgenum; //顶点数,边数
}AMGragh;
int locatevex(AMGragh G, VexType x)
{
    for (int i = 0; i < G.vexnum; i++)//查找顶点信息的下标
        if (x == G.Vex[i])
            return i;
    return -1;//没找到
}
void CreateAMGraph(AMGragh& G)//创建无向图的邻接矩阵
{
    int i, j;
    VexType u, v;
    cout << "请输入顶点数:" << endl;
    cin >> G.vexnum;
    cout << "请输入边数:" << endl;
    cin >> G.edgenum;
    cout << "请输入顶点信息:" << endl;
    for (int i = 0; i < G.vexnum; i++)//输入顶点信息,存入顶点信息数组
        cin >> G.Vex[i];
    for (int i = 0; i < G.vexnum; i++)//初始化邻接矩阵所有值为0,如果是网,则初始化邻接矩阵为无穷大
        for (int j = 0; j < G.vexnum; j++)
            G.Edge[i][j] = 0;
    cout << "请输入每条边依附的两个顶点:" << endl;
    while (G.edgenum--)
    {
        cin >> u >> v;
        i = locatevex(G, u);//查找顶点u的存储下标
        j = locatevex(G, v);//查找顶点v的存储下标
        if (i != -1 && j != -1)
            G.Edge[i][j] = G.Edge[j][i] = 1; //邻接矩阵储置1,若有向图G.Edge[i][j]=1
        else
        {
            cout << "输入顶点信息错!请重新输入!" << endl;
            G.edgenum++;//本次输入不算
        }
    }
}
void print(AMGragh G)//输出邻接矩阵
{
    cout << "图的邻接矩阵为:" << endl;
    for (int i = 0; i < G.vexnum; i++)
    {
        for (int j = 0; j < G.vexnum; j++)
            cout << G.Edge[i][j] << "\t";
        cout << endl;
    }
}
void DFS_AM(AMGragh G, int v)//基于邻接矩阵的深度优先遍历
{
    int w;
    cout << G.Vex[v] << "\t";
    visited[v] = true;
    for (w = 0; w < G.vexnum; w++)//依次检查v的所有邻接点
    {
        if (G.Edge[v][w] && !visited[w])//v,w邻接且w未被访问
        {
            DFS_AM(G, w);//从w顶点开始递归深度优先遍历
        }
    }
}
//如果不是一个连通图,还需要进行验证.
void DFS_AM(AMGragh G)
{
    for (int i = 0; i < G.vexnum; i++)//检查未被访问的顶点
    {
        if (!visited[i])
        {
            DFS_AM(G, i);
        }
    }
}
int main()
{
    int v;
    VexType c;
    AMGragh G;
    CreateAMGraph(G);
    print(G);
    cout << "请输入遍历连通图的起始点: ";
    cin >> c;
    v = locatevex(G, c);//查找顶点u的存储下标
    if (v != -1)
    {
        cout << "深度优先搜索遍历连通图结果: " << endl;
        DFS_AM(G);
    }
    else {
        cout << "输入顶点信息错误!请重新输入!" << endl;
    }
    return 0;
}


运行结果1

如图:


20210226015455292.jpg

2021022601574762.jpg


代码实现2—深度优先遍历 邻接表


//深度优先遍历  邻接表
#include <iostream>
using namespace std;
const int MaxVnum = 100;//顶点数最大值
bool visited[MaxVnum];  //访问标志数组,其初值为"false"
typedef char VexType;//顶点的数据类型为字符型
typedef struct AdjNode { //定义邻接点类型
    int v; //邻接点下标
    struct AdjNode* next; //指向下一个邻接点
}AdjNode;
typedef struct VexNode { //定义顶点类型
    VexType data; // VexType为顶点的数据类型,根据需要定义
    AdjNode* first; //指向第一个邻接点
}VexNode;
typedef struct {//定义邻接表类型
    VexNode  Vex[MaxVnum];
    int vexnum, edgenum; //顶点数,边数
}ALGragh;
int locatevex(ALGragh G, VexType x)
{
    for (int i = 0; i < G.vexnum; i++)//查找顶点信息的下标
        if (x == G.Vex[i].data)
            return i;
    return -1;//没找到
}
void insertedge(ALGragh& G, int i, int j)//插入一条边
{
    AdjNode* s;
    s = new AdjNode;
    s->v = j;
    s->next = G.Vex[i].first;
    G.Vex[i].first = s;
}
void printg(ALGragh G)//输出邻接表
{
    cout << "----------邻接表如下:----------" << endl;
    for (int i = 0; i < G.vexnum; i++)
    {
        AdjNode* t = G.Vex[i].first;
        cout << G.Vex[i].data << ": ";
        while (t != NULL)
        {
            cout << "---->";
            cout << "[" << G.Vex[t->v].data << "]";
            t = t->next;
        }
        cout << endl;
    }
}
void CreateALGraph(ALGragh& G)//创建无向图邻接表
{
    int i, j;
    VexType u, v;
    cout << "请输入顶点数和边数:" << endl;
    cin >> G.vexnum >> G.edgenum;
    cout << "请输入顶点信息:" << endl;
    for (i = 0; i < G.vexnum; i++)//输入顶点信息,存入顶点信息数组
        cin >> G.Vex[i].data;
    for (i = 0; i < G.vexnum; i++)
        G.Vex[i].first = NULL;
    cout << "请依次输入每条边的两个顶点u,v" << endl;
    while (G.edgenum--)
    {
        cin >> u >> v;
        i = locatevex(G, u);//查找顶点u的存储下标
        j = locatevex(G, v);//查找顶点v的存储下标
        if (i != -1 && j != -1)
        {
            insertedge(G, i, j);
            //insertedge(G, j, i);//无向图多插入一条边
        }
        else
        {
            cout << "输入顶点信息错!请重新输入!" << endl;
            G.edgenum++;//本次输入不算
        }
    }
}
void DFS_AL(ALGragh G, int v)//基于邻接表的深度优先遍历
{
    int w;
    AdjNode* p;//辅助指针
    cout << G.Vex[v].data << "\t";
    visited[v] = true;
    p = G.Vex[v].first;//获取该顶点的第一个邻接点
    while (p)//依次检查v的所有邻接点
    {
        w = p->v;//w为v的邻接点.
        if (!visited[w])//w未被访问,则从w出发,递归深度优先遍历
        {
            DFS_AL(G, w);
        }
        p = p->next;
    }
}
void DFS_AL(ALGragh G) //非连通图,基于邻接表的深度优先遍历
{
    for(int i = 0; i < G.vexnum; i++)//检查未被访问的点.
        if (!visited[i])
        {
            DFS_AL(G, i);
        }
}
int main()
{
    ALGragh G;
    int v;
    VexType c;
    CreateALGraph(G);//创建有向图邻接表
    printg(G);//输出
    cout << "请输入遍历连通图的起始点: ";
    cin >> c;
    v = locatevex(G, c);
    if (v != -1)
    {
        cout << "深度优先搜索遍历连通图结果: " << endl;
        DFS_AL(G, v);
        DFS_AL(G);
    }
    else
    {
        cout << "输入顶点信息错误!请重新输入!" << endl;
    }
    return 0;
}


运行结果2

如图:


image.jpeg


20210226020155356.jpg

20210507153441737.png


算法分析


基于邻接矩阵的DFS算法

查找每个顶点的邻接点需要O(n)时间,一共n个顶点,总的时间复杂度为O(n^2),使用了一个递归工作栈,空间复杂度为O(n)。

基于邻接表的DFS算法

查找顶点vi的邻接点需要O(d(vi))时间,d(vi)为vi的出度(无向图为度),对有向图而言,所有顶点的出度之和等于边数e,对无向图而言,所有顶点的度之和等于2e,因此查找邻接点的时间复杂度为O(e),加上初始化时间O(n),总的时间复杂度为O(n+e),(由于n和e大小未知,所以时间复杂度两者都涉及)使用了一个递归工作栈,空间复杂度为O(n)。


二.广度优先遍历


2.1介绍


广度优先搜索(Breadth First Search, BFS),又称宽度优先搜索,是最常见的图搜索方法之一。广度优先搜索是从某个顶点(源点)出发,一次性访问所有未被访问的邻接点,再依次从这些访问过的邻接点出发……似水中涟漪,一层层地传播开来.


举例

20210226020816152.jpg

20210226020830481.jpg

广度优先遍历秘籍:先被访问的顶点,其邻接点先被访问。

根据广度优先遍历秘籍,先来先服务,可以借助于队列实现。每个节点访问一次且只访问一次,因此可以设置一个辅助数组visited[i]=false,表示第i个顶点未访问;visited[i]=true,表示第i个顶点已访问。


代码实现1—广度优先遍历邻接矩阵

#include <iostream>
#include <queue>//引入队列头文件
using namespace std;
#define MaxVnum 100  //顶点数最大值
bool visited[MaxVnum];  //访问标志数组,其初值为"false"
typedef char VexType;  //顶点的数据类型,根据需要定义
typedef int EdgeType;  //边上权值的数据类型,若不带权值的图,则为0或1
typedef struct {
    VexType Vex[MaxVnum];
    EdgeType Edge[MaxVnum][MaxVnum];
    int vexnum, edgenum; //顶点数,边数
}AMGragh;
int locatevex(AMGragh G, VexType x)
{
    for (int i = 0; i < G.vexnum; i++)//查找顶点信息的下标
        if (x == G.Vex[i])
            return i;
    return -1;//没找到
}
void CreateAMGraph(AMGragh& G)//创建有向图的邻接矩阵
{
    int i, j;
    VexType u, v;
    cout << "请输入顶点数:" << endl;
    cin >> G.vexnum;
    cout << "请输入边数:" << endl;
    cin >> G.edgenum;
    cout << "请输入顶点信息:" << endl;
    for (int i = 0; i < G.vexnum; i++)//输入顶点信息,存入顶点信息数组
        cin >> G.Vex[i];
    for (int i = 0; i < G.vexnum; i++)//初始化邻接矩阵所有值为0,如果是网,则初始化邻接矩阵为无穷大
        for (int j = 0; j < G.vexnum; j++)
            G.Edge[i][j] = 0;
    cout << "请输入每条边依附的两个顶点:" << endl;
    while (G.edgenum--)
    {
        cin >> u >> v;
        i = locatevex(G, u);//查找顶点u的存储下标
        j = locatevex(G, v);//查找顶点v的存储下标
        if (i != -1 && j != -1)
            G.Edge[i][j] = G.Edge[j][i] = 1; //邻接矩阵储置1,若无向图G.Edge[i][j]=G.Edge[j][i]=1
        else
        {
            cout << "输入顶点信息错!请重新输入!" << endl;
            G.edgenum++;//本次输入不算
        }
    }
}
void print(AMGragh G)//输出邻接矩阵
{
    cout << "图的邻接矩阵为:" << endl;
    for (int i = 0; i < G.vexnum; i++)
    {
        for (int j = 0; j < G.vexnum; j++)
            cout << G.Edge[i][j] << "\t";
        cout << endl;
    }
}
void BFS_AM(AMGragh G, int v)
{
    int u, w;
    queue<int> Q;//创建一个队列,里面存放节点的下标
    cout << G.Vex[v] << "\t";
    visited[v] = true;
    Q.push(v);//源点v入队
    while (!Q.empty())
    {
        u = Q.front();//取对头
        Q.pop();
        for (w = 0; w < G.vexnum; w++)//一次检查u的所有邻接点
        {
            if (G.Edge[u][w] && !visited[w])//是邻接点但没有访问则进行访问
            {
                cout << G.Vex[w] << "\t";
                visited[w] = true;
                Q.push(w);
            }
        }
    }
}
void BFS_AM(AMGragh G){
  for(int i = 0; i < G.vexnum; i++){
    if(!visited[i]){
      BFS_AM(G,i); 
    }
  }
}
int main()
{
    int v;
    VexType c;
    AMGragh G;
    CreateAMGraph(G);
    print(G);
    cout << "请输入遍历连通图的起始点: ";
    cin >> c;
    v = locatevex(G, c);//查找顶点u的存储下标
    if (v != -1)
    {
        cout << "广度优先搜索遍历连通图结果: " << endl;
        BFS_AM(G, v);
        BFS_AM(G);
    }
    else {
        cout << "输入顶点信息错! 请重新输入!" << endl;
    }
    return 0;
}


运行结果1

如图:

202102260212018.jpg

20210226021210847.jpg

2021050716043349.png

20210507160503578.png


代码实现2—广度优先遍历邻接表

#include <iostream>
#include <queue>//引入队列头文件
using namespace std;
const int MaxVnum = 100;//顶点数最大值
bool visited[MaxVnum];  //访问标志数组,其初值为"false"
typedef char VexType;//顶点的数据类型为字符型
typedef struct AdjNode { //定义邻接点类型
    int v; //邻接点下标
    struct AdjNode* next; //指向下一个邻接点
}AdjNode;
typedef struct VexNode { //定义顶点类型
    VexType data; // VexType为顶点的数据类型,根据需要定义
    AdjNode* first; //指向第一个邻接点
}VexNode;
typedef struct {//定义邻接表类型
    VexNode  Vex[MaxVnum];
    int vexnum, edgenum; //顶点数,边数
}ALGragh;
int locatevex(ALGragh G, VexType x)
{
    for (int i = 0; i < G.vexnum; i++)//查找顶点信息的下标
        if (x == G.Vex[i].data)
            return i;
    return -1;//没找到
}
void insertedge(ALGragh& G, int i, int j)//插入一条边
{
    AdjNode* s;
    s = new AdjNode;
    s->v = j;
    s->next = G.Vex[i].first;
    G.Vex[i].first = s;
}
void printg(ALGragh G)//输出邻接表
{
    cout << "----------邻接表如下:----------" << endl;
    for (int i = 0; i < G.vexnum; i++)
    {
        AdjNode* t = G.Vex[i].first;
        cout << G.Vex[i].data << ": ";
        while (t != NULL)
        {
            cout << "---->";
            cout << "[" << G.Vex[t->v].data << "]";
            t = t->next;
        }
        cout << endl;
    }
}
void CreateALGraph(ALGragh& G)//创建有向图邻接表
{
    int i, j;
    VexType u, v;
    cout << "请输入顶点数和边数:" << endl;
    cin >> G.vexnum >> G.edgenum;
    cout << "请输入顶点信息:" << endl;
    for (i = 0; i < G.vexnum; i++)//输入顶点信息,存入顶点信息数组
        cin >> G.Vex[i].data;
    for (i = 0; i < G.vexnum; i++)
        G.Vex[i].first = NULL;
    cout << "请依次输入每条边的两个顶点u,v" << endl;
    while (G.edgenum--)
    {
        cin >> u >> v;
        i = locatevex(G, u);//查找顶点u的存储下标
        j = locatevex(G, v);//查找顶点v的存储下标
        if (i != -1 && j != -1)
            insertedge(G, i, j);
        else
        {
            cout << "输入顶点信息错!请重新输入!" << endl;
            G.edgenum++;//本次输入不算
        }
    }
}
void BFS_AL(ALGragh G, int v)//基于邻接表的广度优先遍历
{
    int u, w;
    AdjNode* p;
    queue<int> Q;
    cout << G.Vex[v].data << "\t";
    visited[v] = true;
    Q.push(v);
    while (!Q.empty())
    {
        u = Q.front();//取对头元素
        Q.pop();
        p = G.Vex[u].first;
        while (p)
        {
            w = p->v;
            if (!visited[w])//w未被访问
            {
                cout << G.Vex[w].data << "\t";
                visited[w] = true;
                Q.push(w);
            }
            p = p->next;
        }
    }
}
void BFS_AL(ALGragh G)//非连通图,基于邻接表的广度优先遍历
{
    for (int i = 0; i < G.vexnum; i++)//查漏点.
    {
        if (!visited[i])//未被访问则进行访问
        {
            BFS_AL(G, i);
        }
    }
}
int main()
{
    ALGragh G;
    int v;
    VexType c;
    CreateALGraph(G);//创建有向邻接表
    printg(G);//输出
    cout << "请输入遍历连通图的起始点: ";
    cin >> c;
    v = locatevex(G, c);//查找顶点u的存储下标
    if (v != -1)
    {
        cout << "广度优先搜索遍历连通图结构: " << endl;
        BFS_AL(G, v);
        BFS_AL(G);
    }
    else
    {
        cout << "输入顶点错!请重新输入!" << endl;
    }
    return 0;
}


运行结果2

如图:


20210226021359432.jpg

20210226021433680.jpg


算法分析


基于邻接矩阵的BFS算法

查找每个顶点的邻接点需要O(n)时间,一共n个顶点,总的时间复杂度为O(n^2),使用了一个辅助队列,最坏的情况下每个顶点入队一次(访问完就入队),空间复杂度为O(n)。

基于邻接表的BFS算法

查找顶点vi的邻接点需要O(d(vi))时间,d(vi)为vi的出度(无向图为度),对有向图而言,所有顶点的出度之和等于边数e,对无向图而言,所有顶点的度之和等于2e,因此查找邻接点的时间复杂度为O(e),加上初始化时间O(n),总的时间复杂度为O(n+e),使用了一个辅助队列,最坏的情况下每个顶点入队一次,空间复杂度为O(n)。


总结


容易发现,广度优先和深度优先的算法效率基本相同,在实际应用中要根据需要合理选择.

需要注意的是,一个图的邻接矩阵是唯一的,因此基于邻接矩阵的BFS或DFS遍历序列也是唯一的。而图的邻接表不是唯一的,边的输入顺序不同,正序或逆序建表都会影响邻接表的邻接点顺序,因此基于邻接表的BFS或DFS遍历序列不是唯一的。


相关文章
|
5月前
|
存储
第5章 图的遍历
第5章 图的遍历
|
存储 人工智能 算法
图的遍历算法
图的遍历算法
深度优先遍历与广度优先遍历
深度优先遍历与广度优先遍历
128 0
拓扑排序(邻接表实现)
拓扑排序(邻接表实现)
181 0
拓扑排序(邻接表实现)
|
算法
最小生成树:Kruskal算法(邻接表+最小堆+并查集)
最小生成树:Kruskal算法(邻接表+最小堆+并查集)
259 0
最小生成树:Kruskal算法(邻接表+最小堆+并查集)
|
机器学习/深度学习 存储
图的遍历 无向图深搜 宽搜
图的遍历 无向图深搜 宽搜
图的遍历 无向图深搜 宽搜
|
存储 算法
图的广度优先搜索和深度优先搜索(邻接链表表示)
邻接表表示法将图以邻接表(adjacency lists)的形式存储在计算机中。所谓图的邻接表,也就是图的所有节点的邻接表的集合;而对每个节点,它的邻接表就是它的所有出弧。邻接表表示法就是对图的每个节点,用一个单向链表列出从该节点出发的所有弧,链表中每个单元对应于一条出弧。为了记录弧上的权,链表中每个单元除列出弧的另一个端点外,还可以包含弧上的权等作为数据域。图的整个邻接表可以用一个指针数组表示。例如下图所示,邻接表表示为
223 0
图的广度优先搜索和深度优先搜索(邻接链表表示)
|
算法
无向图邻接表(深度优先算法)
无向图邻接表(深度优先算法)
126 0