6.1图的存储

6.4图的存储

图的存储
顺序存储邻接矩阵、边集数组
链式存储邻接表、链式前向星、十字链表、邻接多重表

1.邻接矩阵

邻接矩阵(Adjacency Matrix)是表示顶点之间相邻关系的矩阵。设G(V, E)是具有n个顶点的图,则G的邻接矩阵是具有如下性质的n阶方阵

img

若G是网,则邻接矩阵可以定义为

img

其中,wi, j表示边上的权值;表示计算机允许的、大于所有边上权值的数。例如,图6.10所示为一个有向网和它的邻接矩阵。

img

1. 1邻接矩阵的表示方法

无向图、有向图和网的邻接矩阵

1)

M[i] [j]举例图示特点
无向图的邻接矩阵若从节点vi到节点vj有边,则邻接矩阵M[i] [j]=M[j] [i]=1,否则M[i] [j]=0。image-20231113232125218image-20231113233053925(1)对称矩阵且唯一(2)第i行或第i列非零元素的个数=第i个节点的度。上图中的邻接矩阵,第3列非零元素的个数为2,说明第3个节点c的度为2。
有向图的邻接矩阵若从节点vi到节点vj有边,则邻接矩阵M[i] [j]=1,否则M[i] [j]=0。(不必考虑M[j] [i])image-20231113232645490image-20231113231830483(1)邻接矩阵不一定对称(2)第i行非零元素的个数=第i个节点的出度,第i列非零元素的个数=第i个节点的入度。上图中的邻接矩阵,第3行非零元素的个数为2,第3列非零元素的个数也为2,说明第3个节点c的出度和入度均为2。
网的邻接矩阵网是带权图,需要存储边的权值,则邻接矩阵表示为image-20231113232749698wij表示边上的权值,∞表示无穷大。当i=j时,wii也可被设置为0。image-20231113233303028在该网中,从节点a到节点b有边,且该边的权值为2,节点a、b在一维数组中的存储位置分别为0、1,因此M[0] [1]=2。从节点b到节点a没有边,因此M[1] [0]=∞。

注意:以尖括号<vi,vj>表示的是有序对,以圆括号(vi,vj)表示的是无序对,后同。

image-20231113235234858
1.1图的邻接矩阵存储表示
//-----图的邻接矩阵存储表示-----
#include "../Define.h"
#define MaxInt 32767                    //表示极大值,即∞(只是因为不存在这个很大的值,所以就给一个数值)
//#define MaxInt 6
#define MVNum 100                       //最大顶点数
typedef char VerTexType;                //假设顶点的数据类型为字符型
typedef int ArcType;                    //假设边的权值类型为整型
typedef struct
{
    VerTexType vexs[MVNum];              //顶点表
    ArcType arcs[MVNum][MVNum];          //邻接矩阵
    int vexnum,arcnum;                   //图的当前点数和边数
}AMGraph;//Adjacency Matrix Graph
1.2邻接矩阵的数据结构定义
`typedef struct
{
    VerTexType vexs[MVNum];              //顶点表
    ArcType arcs[MVNum][MVNum];          //邻接矩阵
    int vexnum,arcnum;                   //图的当前点数和边数
}AMGraph;//Adjacency Matrix Graph`

image-20231114132441275

1.2采用邻接矩阵表示法创建无向网

已知一个图的点和边,使用邻接矩阵表示法来创建此图的方法比较简单,下面以一个无向网为例来说明创建图的算法。

算法步骤

(1)输入节点数和边数;

(2)依次输入节点信息,将其存储到节点数组Vex[]中;

(3)初始化邻接矩阵,如果是图,则将其初始化为0;如果是网,则将其初始化为∞;

(4)依次输入每条边依附的两个节点,如果是网,则还需要输入该边的权值。

​ • 如果是无向图,则输入ab,查询节点a、b在节点数组Vex[]中的存储下标i、j,令Edge[i] [j]=Edge[j] [i]=1。

​ • 如果是有向图,则输入ab,查询节点a、b在节点数组Vex[]中的存储下标i、j,令Edge[i] [j]=1。

​ • 如果是无向网,则输入a b w,查询节点a、b在节点数组Vex[]中的存储下标i、j,令Edge[i] [j]=Edge[j] [i]=w。

​ • 如果是有向网,则输入a b w,查询节点a、b在节点数组Vex[]中的存储下标i、j,令Edge[i] [j]=w。

1.2.1无向图/网的完美图解和效果:

一个无向图如下图所示,其邻接矩阵的存储过程如下所述。(参考《算法训练营:海量图解+竞赛刷题(入门篇)》6.1)

image-20231113234010044

image-20231113233511993image-20231113235312100image-20231113234816714

1.2.2邻接矩阵的代码实现
// Created by lijing on 23-11-13.
//-----图的邻接矩阵存储表示-----
#include "../Define.h"
#define MaxInt 32767                    //表示极大值,即∞(只是因为不存在这个很大的值,所以就给一个数值)
//#define MaxInt 6
#define MVNum 100                       //最大顶点数
typedef char VerTexType;                //假设顶点的数据类型为字符型
typedef int ArcType;                    //假设边的权值类型为整型
typedef struct
{
    VerTexType vexs[MVNum];              //顶点表
    ArcType arcs[MVNum][MVNum];          //邻接矩阵
    int vexnum,arcnum;                   //图的当前点数和边数
}AMGraph;//Adjacency Matrix Graph
/**2.采用邻接矩阵表示法创建无向网
【算法步骤】
  ① 输入总顶点数和总边数。
  ② 依次输入点的信息存入顶点表verxs[i]中。
  ③ 初始化邻接矩阵,使每个权值初始化为极大值MaxInt。
  ④ 构造邻接矩阵。依次输入每条边依附的顶点和其权值,
 确定两个顶点在图中的位置之后,使相应边赋予相应的权值,同时使其对称边赋予相同的权值。
 **/

//在图中查找顶点
int LocateVex(AMGraph G,VerTexType x){
    for (int i = 0; i < G.vexnum; ++i) {//查找顶点信息的下标
        if(x == G.vexs[i]) return i;
    }
    return -1;//没找到
}
//输出邻接矩阵
void print(AMGraph G){
    cout<<"图的邻接矩阵为:\n";
    for (int i = 0; i < G.vexnum; ++i) {
        for (int j = 0; j < G.vexnum; ++j) {
            cout<<G.arcs[i][j]<<"\t";
        }
        cout<<endl;
    }
}
//将最大值
void printGraph(AMGraph G){
    cout<<"邻接矩阵:"<<endl;
    for(int i=0;i<G.vexnum;++i){
        for(int j=0;j<G.vexnum;++j){
            if(j!=G.vexnum-1){
                if(G.arcs[i][j]!=MaxInt){
                    cout<<G.arcs[i][j]<<"\t";
                }
                else{cout<<"∞"<<"\t";}
            }
            else
            {
                if(G.arcs[i][j]!=MaxInt){
                    cout<<G.arcs[i][j]<<endl;
                }
                else{
                    cout<<"∞"<<endl;
                }
            }
        }
    }
    cout<<endl;
}
Status CreateUDN(AMGraph &G)
{//采用邻接矩阵表示法,创建无向网G
    int i,j,k;
    cout<<"请输入节点数 边数:";
    cin>>G.vexnum>>G.arcnum;                //输入总顶点数,总边数

    for(i=0;i<G.vexnum;i++) {                 //依次输入点的信息,存在一维数组里
        cout<<"请输入第"<<i+1<<"个节点:";
        cin >> G.vexs[i];
    }
    cout<<"顶点数组"<<endl;
    for(i=0;i<G.vexnum;++i)
    {
        cout<<G.vexs[i]<<"\t";
    }
    cout<<endl;

    for(i=0;i<G.vexnum;i++)                 //初始化邻接矩阵所有值为0,如果是网则初始化边的权值均置为极大值MaxInt,
        for(int j=0;j<G.vexnum;j++)
            G.arcs[i][j]=MaxInt;
         // G.arcs[i][j]=0;
    cout<<"初始化的邻接矩阵为MaxInt"<<endl;
    print(G);
    for(k=0;k<G.arcnum;++k)                 //构造邻接矩阵
    {
        VerTexType v1, v2;//傻逼在这里声明了int类型是不对的!!
        ArcType w;//图则w=1,网则w=权值,可以定死也可以输入
        cout<<k+1<< "请依次输入顶点1 顶点2 权值"<<endl;
        cin >> v1 >> v2 >> w;                      //输入一条边依附的顶点v1,v2及权值w
        i = LocateVex(G, v1);
        j = LocateVex(G, v2); //确定v1和v2在G中的位置,即顶点数组的下标
        if (i != -1 && j != -1) {
            G.arcs[i][j] = w;                     //边<v1, v2>的权值置为w
            G.arcs[j][i] = G.arcs[i][j];          //置<v1, v2>的对称边<v2, v1>的权值为w
        }
        else
        {
            cout << "输入顶点信息错!请重新输入!"<<endl;
            k--;//本次输入不算
        }
    }
    return OK;
}

int main(){
    AMGraph G;
    CreateUDN(G);
    print(G);
    printGraph(G);
    return 0;
}
//-------#include "../Define.h"-------
#define OK 1
#define ERROR 0
#define OVERFLOW -2
typedef int Status;

#include <stdio.h>
#include <stdlib.h>
#include <string.h>//strcpy()的库
#include <iostream>
using namespace std;
#include <assert.h>//断言函数,不符合要求的会输出警告

2.邻接表

2.1邻接表的存储表示

邻接表图例特点
无向图邻接表一个节点的所有邻接点构成一个单链表image-20231115113146599(1)如果无向图有n个节点、e条边,则节点表有n个节点,邻接点表有2e个节点。(2)节点的度为该节点后面单链表中的节点数。
有向图的邻接表(出弧)注意:对有向图中节点的邻接点,只看该节点的出边(出弧)。image-20231115112105053(1)如果有向图有n个节点、e条边,则节点表有n个节点,邻接点表有e个节点。(2)节点的出度为该节点后面单链表中的节点数。(3)找节点出度易,但是找入度很难。
有向图的逆邻接表(入弧)为了方便得到节点的入度,可以建立一个有向图的逆邻接表:image-20231114140429731(1)如果有向图有n个节点、e条边,则节点表有n个节点,邻接点表有e个节点。(2)节点的入度为该节点后面的单链表中的节点数。

有向图的邻接表(出弧)解释:

• 节点a的邻接点(只看出边,即出弧)是节点b、c、e,其邻接点的存储下标为1、2、4,按照头插法(逆序)将其放入节点a后面的单链表中;

• 节点b的邻接点是节点c,其邻接点的存储下标为2,将其放入节点b后面的单链表中;

• 节点c的邻接点是节点d、e,其邻接点的存储下标为3、4,按头插法将其放入节点c后面的单链表中;

• 节点d的邻接点是节点e,其邻接点的存储下标为4,将其放入节点d后面的单链表中;

• 节点e没有邻接点,其后面的单链表为空。

有向图的逆邻接表(入弧)解释:

• 节点a没有逆邻接点(只看入边,即入弧),其后面的单链表为空;

• 节点b的逆邻接点是节点a,其邻接点的存储下标为0,将其放入节点b后面的单链表中;

• 节点c的逆邻接点是a、b,其邻接点的存储下标为0、1,按照头插法将其放入节点c后面的单链表中;

• 节点d的逆邻接点是节点c,其邻接点的存储下标为2,将其放入节点d后面的单链表中;

• 节点e的逆邻接点是节点a、c、d,其邻接点的存储下标为0、2、3,按照头插法(逆序)将其放入节点e后面的单链表中。注意:对有向图中节点的逆邻接点,只看该节点的入边(入弧)。

2.2图的邻接表存储表示和结构定义

//- - - - -图的邻接表存储表示- - - - -
#define MVNum 100                         //最大顶点数
typedef char VerTexType;                //假设顶点的数据类型为字符型
typedef int ArcType;
typedef char OtherInfo;
//边结点:边指向的顶点的位置+结点(包含指向下一个边结点的指针)+其他信息(比如权值)
typedef struct ArcNode
{
    int adjvex;                           //该边所指向的顶点的位置
    struct ArcNode * nextarc;             //指向下一条边/邻接点的指针
    OtherInfo info;                       //和边相关的信息
}ArcNode;
//顶点:数据+结点(指向边结点的指针)
typedef struct VNode
{
    VerTexType data;
    ArcNode *firstarc;                   //指向第一条依附该顶点的边的指针
}VNode,AdjList[MVNum];                  //AdjList表示邻接表类型,[MVNum]表示的一个数组
//图的邻接表的结构定义:顶点数组,顶点数和边(边结点)数
//顶点数组里的每一个顶点后面跟着链表,在遍历的时候注意即可
typedef struct
{
    AdjList vertices;                  //vertex的复数
    int vexnum,arcnum;                 //图的当前顶点数和边数
}ALGraph;

2.3采用邻接表表示法创建有向图

2.3.1有向图的完美图解及运行效果

image-20231115215344580

image-20231126204830632

2.3.2代码实现

// Created by lijing on 23-11-14.
#include "../Define.h"
//- - - - -图的邻接表存储表示- - - - -
#define MVNum 100                         //最大顶点数
typedef char VerTexType;                //假设顶点的数据类型为字符型
typedef int ArcType;
typedef char OtherInfo;
//边结点:边指向的顶点的位置+结点(包含指向下一个边结点的指针)+其他信息(比如权值)
typedef struct ArcNode                    
{ 
    int adjvex;                           //该边所指向的顶点的位置
    struct ArcNode * nextarc;             //指向下一条边/邻接点的指针
    OtherInfo info;                       //和边相关的信息
}ArcNode;
//顶点:数据+结点(指向边结点的指针)
typedef struct VNode                     
{
    VerTexType data;
    ArcNode *firstarc;                   //指向第一条依附该顶点的边的指针
}VNode,AdjList[MVNum];                  //AdjList表示邻接表类型,[MVNum]表示的一个数组
//图的邻接表的结构定义:顶点数组,顶点数和边(边结点)数
//顶点数组里的每一个顶点后面跟着链表,在遍历的时候注意即可
typedef struct
{
    AdjList vertices;                  //vertex的复数
    int vexnum,arcnum;                 //图的当前顶点数和边数
}ALGraph;
//在邻接表中的顶点数组里查找顶点
int LocateVex(ALGraph G,VerTexType x){
    for (int i = 0; i < G.vexnum; ++i) {//查找顶点信息的下标
        if(x == G.vertices[i].data) return i;
    }
    return -1;//没找到
}
//插入一个边结点(头插法)a=i,b=j
void insertArcNode(ALGraph &G,int a,int b){
    ArcNode *v=new ArcNode;
    v->adjvex=b;
    v->nextarc=G.vertices[a].firstarc;
    G.vertices[a].firstarc=v;
}
//打印图的邻接表
void printGraph(ALGraph G){
    cout<<"图的邻接表为:\n";
    //本质是链表的遍历
    for (int i = 0; i < G.vexnum; ++i) {
        cout<<G.vertices[i].data<<"\t";
        ArcNode *p=G.vertices[i].firstarc ;
        while (p){
            cout<<p->adjvex<<"\t";
            p=p->nextarc;//修改指针
        }
        cout<<endl;
    }
}
/*
void ListTraverse(LinkList L) {
    cout<<"遍历链表:\n";
    LNode *p=L->next; //p从第一个数据结点(首元结点)开始
//    cout<<"输出数据元素的值:\n";
    while (p){
        cout<<p->data;
        cout<<"\n";
        p=p->next;//修改指针
    }
}*/
Status CreateUDG(ALGraph &G)
{//采用邻接表表示法,创建无向图G
    int i,j,k;
    cout<<"请输入总节点数 边数:";
    cin>>G.vexnum>>G.arcnum;               //输入总顶点数,总边数

    for(i=0;i<G.vexnum;++i)                //输入各点,构造表头结点表
    {
        cout<<"请输入第"<<i+1<<"个节点:";
        cin>> G.vertices[i].data;           //输入顶点值:图G中第i个顶点的值
        G.vertices[i].firstarc=NULL;       //初始化表头结点的指针域为NULL
    }         //for

    cout<<"顶点数组"<<endl;
    for(i=0;i<G.vexnum;++i)
    {
        cout<<G.vertices[i].data<<"\t";
    }
    cout<<endl;

    for(k=0;k<G.arcnum;++k)               //输入各边,构造邻接表
    {
        VerTexType v1,v2;
        cout<<k+1<< "请依次输入顶点1 顶点2"<<endl;
        cin>>v1>>v2;                       //输入一条边依附的两个顶点
        i=LocateVex(G,v1); j=LocateVex(G,v2);
        //确定v1和v2在G中位置,即顶点在G.vertices中的序号
        if (i!=-1&&j!=-1){
            insertArcNode(G,i,j);
            printGraph(G);
//            insertArcNode(G,j,i);//无向图还需要插入一条边
        }

        /*//生成一个新的边结点*p1
        ArcNode *p1=new ArcNode;
        p1->adjvex=j;                     //邻接点序号为j
        p1->nextarc=G.vertices[i].firstarc;
        G.vertices[i].firstarc=p1;
        //将新结点*p1插入顶点vi的边表头部

        //无向图:对称所以再重复一次,有向图就无需下方
        ArcNode *p2=new ArcNode;                   //生成另一个对称的新的边结点*p2
        p2->adjvex=i;                     //邻接点序号为i
        p2->nextarc=G.vertices[j].firstarc;
        G.vertices[j].firstarc=p2;
        //将新结点*p2插入顶点vj的边表头部*/
    }                                    //for
    return OK;
}

int main(){
    ALGraph G;
    CreateUDG(G);
    printGraph(G);
    return 0;
}

*.邻接矩阵与邻接表表示法的关系和比较

优点缺点对于任一确定的无向图空间复杂度使用使用DFS遍历的效率
邻接矩阵快速判断在两节点之间是否有边。在图中,Edge[i] [j]=1,表示有边;Edge[i] [j]=0,表示无边。在网中,Edge[i] [j]=∞表示无边。时间复杂度为O(1)。•方便计算各节点的度。在无向图中,邻接矩阵第i行元素之和=节点i的度;在有向图中,第i行元素之和=节点i的出度,第i列元素之和=节点i的入度。时间复杂度为O(n)。不便于增删节点:需要改变邻接矩阵的大小;• 不便于访问所有邻接点。访问第i个节点的所有邻接点时,需要访问第i行的所有元素,时间复杂度为O(n)。访问所有节点的邻接点,时间复杂度为O(n2)。• 浪费空间—存稀疏图(点多边少,无效元素多),O(n2)。唯一(行列号与顶点编号一致)O(n2)稠密图遍历每一个顶点都要重头扫描
邻接表• 便于增删节点。• 便于访问所有邻接点。访问所有节点的邻接点,其时间复杂度为O(n+e)。• 空间复杂度低。节点表占用n个空间,无向图的邻接点表占用n+2e个空间,有向图的邻接点表占用n+e个空间,总体空间复杂度为O(n+e)。而邻接矩阵的空间复杂度为O(n2)。• 无向图中方便判断任一顶点的“度”:• 不便于判断在两个节点之间是否有边(需要遍历该节点后面的邻接点链表);• 有向图邻接表中只能计算出度;需要构造“逆邻接表”(存指向自己的边)来方便计算“入度”。不唯一(链接次序与顶点编号无关)O(n+e)稀疏图有2e个表节点,但只需要扫描e个结点即可完成遍历
n:顶点表节点;e:边表结点;稠密图适于在邻接矩阵上进行深度遍历;稀疏图适于在邻接表上进行深度遍历虽然以邻接表访问单条边的效率不高,但是访问同一节点的所有关联边时,仅需访问该节点后面的单链表,时间复杂度为该节点的度O(d(vi));而以邻接矩阵访问同一节点的所有关联边时,时间复杂度为O(n)。总体上,邻接表比邻接矩阵效率更高。

image-20231114215639364

更新:

image-20231115120508598

4.十字链表

4.1十字链表的存储结构表示

//- - - - -有向图的十字链表存储表示- - - - -
typedef char VerTexType;                //假设顶点的数据类型为字符型
typedef int ArcType;
typedef char InfoType;
#define MAX_VERTEX_NUM  20
typedef struct ArcBox
{
    int tailvex,headvex;                 //该弧的尾和头顶点的位置
    struct ArcBox *hlink, *tlink;        //分别为弧头相同和弧尾相同的弧的链域
    InfoType *info;                      //该弧相关信息的指针
}ArcBox;
typedef struct VexNode
{
    VerTexType data;
    ArcBox *firstin,*firstout;           //分别指向该顶点第一条入弧和出弧
}VexNode;
typedef struct
{
    VexNode xlist[MAX_VERTEX_NUM];       //表头向量
    int vexnum, arcnum;                  //有向图的当前顶点数和弧数
}OLGraph;

4.2十字链表的存储流程图解

1.顺序:a的出度 、入度

image-20231115120508598

2.b的出度没有,入度有两个

image-20231115125706287

3.c的出度、入度

image-20231115125844846

5.邻接多重表

image-20231115144511810

6.5图的遍历

图的遍历也是从 图中某一顶点出发,按照某种方法对图中所有顶点访问且仅访问一次。图的遍历算法是求解图的连通性问题、拓扑排序和关键路径等算法的基础。

类似于树的连通图非连通图邻接矩阵邻接表空间复杂度时间复杂度
深度优先遍历(DFS)先序遍历相同相同
广度优先遍历(BFS)层次遍历

1深度优先搜索

image-20231115125918186

image-20231115143242060

​ 图6.17 遍历图的过程

(1)从顶点v1出发,访问v1。(2)在访问了顶点v1之后,选择第一个未被访问的邻接点v2,访问v2。以v2为新顶点,重复此步,访问v4,v8、v5。在访问了v5之后,由于v5的邻接点都已被访问,此步结束。(3)搜索从v5回到v8,由于同样的理由,搜索继续回到v4,v2直至v1,此时由于v1的另一个邻接点未被访问,则搜索又从v1到v3,再继续进行下去。由此,得到的顶点访问序列为:v1→v2→v4→v8→v5→v3→v6→v7

图6.17(b)中所示的所有顶点加上标有实箭头的边,构成一棵以v1为根的树,称为深度优先生成树,如图6.18(a)所示。

image-20231115183329909

1.1深度优先搜索遍历连通图

若把2当做起点,访问了就把辅助数组的值修改为1,再从邻接点中找一个没有被访问过的 ,切换到对应的行,再选择矩阵中有1的,且访问数组里的值为0的点访问,再把访问数组里的数值修改为1;

1.2深度优先搜索非连通图的遍历

访问完一个连通分量,就访问下一个

1.3深度优先搜索无向图代码和效果

image-20231115144726455

image-20231115203406806

效果:

image-20231115183605683

代码:

bool visited[MVNum];                   //访问标志数组,其初值为“false”
//深度优先遍历by顶点位置
void DFS_AM2(AMGraph G,int v)
{//图G为邻接矩阵类型,从第v个顶点出发深度优先搜索遍历图G
    cout<<G.vexs[v];
    visited[v]=true;  //访问第v个顶点,并置访问标志数组相应分量值为true
    for(int w=0;w<G.vexnum;w++) //依次检查邻接矩阵v所在的行
        if((G.arcs[v][w]!=0)&&(!visited[w])) DFS_AM2(G,w);  //G.arcs[v][w]!=0表示w是v的邻接点,如果w未访问,则递归调用DFS
}
// Created by lijing on 23-11-13.
//-----图的邻接矩阵存储表示-----
#include "../Define.h"
#define MaxInt 32767                    //表示极大值,即∞(只是因为不存在这个很大的值,所以就给一个数值)
//#define MaxInt 6
#define MVNum 100                       //最大顶点数
typedef char VerTexType;                //假设顶点的数据类型为字符型
typedef int ArcType;                    //假设边的权值类型为整型
typedef struct
{
    VerTexType vexs[MVNum];              //顶点表
    ArcType arcs[MVNum][MVNum];          //邻接矩阵
    int vexnum,arcnum;                   //图的当前点数和边数
}AMGraph;//Adjacency Matrix Graph

//在图中查找顶点
int LocateVex(AMGraph G,VerTexType x){
    for (int i = 0; i < G.vexnum; ++i) {//查找顶点信息的下标
        if(x == G.vexs[i]) return i;
    }
    return -1;//没找到
}
//输出邻接矩阵
void printGraph(AMGraph G){
    cout<<"邻接矩阵:"<<endl;
    for(int i=0;i<G.vexnum;++i){
        for(int j=0;j<G.vexnum;++j){
            if(j!=G.vexnum-1){
                if(G.arcs[i][j]!=MaxInt){
                    cout<<G.arcs[i][j]<<"\t";
                }
                else{cout<<"∞"<<"\t";}
            }
            else
            {
                if(G.arcs[i][j]!=MaxInt){
                    cout<<G.arcs[i][j]<<endl;
                }
                else{
                    cout<<"∞"<<endl;
                }
            }
        }
    }
    cout<<endl;
}
bool visited[MVNum];                   //访问标志数组,其初值为“false”
//深度优先遍历by顶点位置
void DFS_AM2(AMGraph G,int v)
{//图G为邻接矩阵类型,从第v个顶点出发深度优先搜索遍历图G
    cout<<G.vexs[v];
    visited[v]=true;  //访问第v个顶点,并置访问标志数组相应分量值为true
    for(int w=0;w<G.vexnum;w++) //依次检查邻接矩阵v所在的行
        if((G.arcs[v][w]!=0)&&(!visited[w])) DFS_AM2(G,w);  //G.arcs[v][w]!=0表示w是v的邻接点,如果w未访问,则递归调用DFS
}

Status CreateUDN(AMGraph &G)
{//采用邻接矩阵表示法,创建无向网G
    int i,j,k;
    cout<<"请输入节点数 边数:";
    cin>>G.vexnum>>G.arcnum;//输入总顶点数,总边数

    for(i=0;i<G.vexnum;i++) {//依次输入点的信息,存在一维数组里
        cout<<"请输入第"<<i+1<<"个节点:";
        cin >> G.vexs[i];
    }
    cout<<"顶点数组"<<endl;
    for(i=0;i<G.vexnum;++i)
    {
        cout<<G.vexs[i]<<"\t";
    }
    cout<<endl;

    for(i=0;i<G.vexnum;i++)   
        for(int j=0;j<G.vexnum;j++)
            G.arcs[i][j]=MaxInt;
         // G.arcs[i][j]=0;  //无向图初始化矩阵w=0
    cout<<"初始化的邻接矩阵为MaxInt"<<endl;
    for(k=0;k<G.arcnum;++k)                 //构造邻接矩阵
    {
        VerTexType v1, v2;//傻逼在这里声明了int类型
        ArcType w;//构建邻接矩阵图则w=1,网则w=权值
        cout<<k+1<< "请依次输入顶点1 顶点2 权值"<<endl;
        cin >> v1 >> v2 >> w;      
        i = LocateVex(G, v1);
        j = LocateVex(G, v2); 标
        if (i != -1 && j != -1) {
            G.arcs[i][j] = w;                     
            G.arcs[j][i] = G.arcs[i][j];
        }
        else
        {
            cout << "输入顶点信息错!请重新输入!"<<endl;
            k--;//本次输入不算
        }
    }
    return OK;
}

int main(){
    AMGraph G;
    CreateUDN(G);
    printGraph(G);
    //深度优先搜索
    int a;
    cout<<"请输入深度遍历的起点位置:";
    cin>>a;
    DFS_AM2(G,a);
    return 0;
}

1.4采用邻接表表示图的深度优先搜索遍历

//未实践版
bool visited[MVNum];                   //访问标志数组,其初值为“false”
void DFS_AL (ALGraph G,int v)
{//图G为邻接表类型,从第v个顶点出发深度优先搜索遍历图G
   cout<<v;
   visited[v]=true;               //访问第v个顶点,并置访问标志数组相应分量值为true
   p=G.vertices[v].firstarc;              //p指向v的边链表的第一个边结点
   while(p!=NULL)                         //边结点非空
   {
     w=p->adjvex;                         //表示w是v的邻接点
     if(!visited[w]) DFS_AL(G,w);         //如果w未访问,则递归调用DFS_AL
     p=p->nextarc;                        //p指向下一个边结点
   }                                      //while
}

2广度优先搜索

广度优先搜索(Breadth First Search,BFS)遍历类似于树的按层次遍历的过程。

点亮第一个灯后,点亮连接着的三个灯;再去访问这些邻接点的邻接点。

image-20231115145300040

2.1广度优先搜索遍历连通图具体过程

image-20231123135544820

image-20231123140340392

广度优先搜索遍历的过程如下:

(1)从图中某个顶点v出发,访问v。

(2)依次访问v的各个未曾访问过的邻接点。

(3)分别从这些邻接点出发依次访问它们的邻接点,并使“先被访问的顶点的邻接点”先于“后被访问的顶点的邻接点”被访问。重复步骤(3),直至图中所有已被访问的顶点的邻接点都被访问到。

对图a进行广度优先搜索遍历的过程具体过程如下。

(1)从顶点v1出发,访问v1。

(2)依次访问v1的各个未曾访问过的邻接点v2和v3。

(3)依次访问v2的邻接点v4和v5,以及v3的邻接点v6和v7,最后访问v4的邻接点v8。

由于这些顶点的邻接点均已被访问,并且图中所有顶点都被访问,由此完成了图的遍历。得到的顶点访问序列为:v1→v2→v3→v4→v5→v6→v7→v8

图所示的所有顶点加上标有实箭头的边,构成一棵以v1为根的树,称为广度优先生成树.

2.2广度优先搜索遍历连通图算法

广度优先搜索遍历的特点是:尽可能先对横向进行搜索。设x和y是两个相继被访问过的顶点,若当前是以x为出发点进行搜索,则在访问x的所有未曾被访问过的邻接点之后,紧接着是以y为出发点进行横向搜索,并对搜索到的y的邻接点中尚未被访问的顶点进行访问。也就是说,先访问的顶点其邻接点亦先被访问。为此,算法实现时需引进队列保存已被访问过的顶点。

看书去吧,要补队列的实现!!!

image-20231115210446898

从v1开始访问,下标为0入队,尾指针R后移,Visited[0]变为1,访问v1:顺着头结点访问第一条弧1、第二条弧2,弧1是v2顶点入队(下标为1入队),弧3是v3顶点入队(下标为2入队),尾指针后移。v1访问完了后数字0出队。

访问v2时:visitd[1]=1,看邻接点,弧0已访问,弧3即v4顶点未访问入队(visited[3]=1,数字3入队),弧4即v5顶点未访问入队(visited[4]=1,数字4入队),v2访问完了数字1出队。

image-20231115210412381

image-20231123142050641

访问v3:看邻接点,弧0,弧5即v6未访问入队(visited[5]=1),弧6即v7未访问入队(visited[6]=1,下标6入队),v3访问完出队。

访问栈中的下一个顶点即v4,弧1已访问,弧7即v8未访问入队(visited[7]=1,下标7入队),v4访问完数字3出队,如下图。

image-20231123140739666

访问栈中的下一个顶点即v5,弧1已访问,弧7已访问,v5访问完数字4出队,F上移。

访问栈中的下一个顶点即v6,弧2已访问,弧5已访问,v6访问完数字5出队。

访问栈中的下一个顶点即v7,

2.3非连通图的广度优先遍历次序

链表

6.6图的应用

现实生活中的许多问题都可以转化为图来解决:

以最小成本构建一个通信网络|计算地图中两地之间的最短路径|复杂活动中各子任务的完成寻找一个较优的顺序

1最小生成树

概念回顾:

生成树:第2、3、4是图的子图,是第一张图的生成树。

image-20231115214136503

注意:含有n个顶点,n-1条边的不一定是生成树。

无向图的生成树:

image-20231123143440634

image-20231115214842914

典型应用:

image-20231115215207527

2最短路径

3拓扑排序

  • 15
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值