一文搞定图的存储表示及DFS(无向图、有向图、网的邻接矩阵及邻接表)

3 篇文章 0 订阅

图是由顶点的有穷集合V(G)和边的有穷集合E(G)组成的,用G=(V,E)表示图,是多对多关系,其中分为有向图和无向图,带权图又被称为网。

存储表示

图常用的存储表示----------邻接矩阵法和邻接表法。

邻接矩阵

图有N个顶点,那么这个图的邻接矩阵是一个N*N的二维数组。代码中设置两点没边,则这两点对应的二维数组值为0,其他有边的两点看是否为带权图,是则二维数组值就为两点的权值,不是则用一个特定的数字来代表这两点有边,当然还要判断这个图是有向图还是无向图了,具体实现看代码注释。

#include <iostream>
using namespace std;

typedef char VertexType; //顶点的类型
typedef int EdgeType; //边的权值类型
#define MaxVex 100 //定义最大定点数
#define Infinity (~(1<<(sizeof(int)*8-1)))
/*
表示1左移4*8-1位 然后取反,
int(int为四字节时,int在有的电脑上不是四字节)的最大值,
当两点之间不邻接时,将两点之间的权值设为int的最大值,
在输出时,用“∞”表示。
*/

//邻接矩阵定义
typedef struct 
{
 VertexType Vexs[MaxVex]; //顶点数组
 EdgeType   Edge[MaxVex][MaxVex]; //边的权值信息数组
 int numV; //当前的顶点数
 int numE; //当前的边数
}MGraph;

//创建图的邻接矩阵
void CreateMGraph(MGraph* G)
{
 char vi, vj;//vi,vj用来存储顶点
 int e;
 printf("请输入图的顶点数和边数(顶点数 边数):");
 cin>>G->numV>>G->numE;
 for (int i = 0; i < G->numV; i++)//矩阵的初始化
 {
  for (int j = 0; j < G->numV; j++)
  {
   if (i == j)
   {
    G->Edge[i][j] = 0;//将顶点与自身的权值初始化为0
   }
   else
   {
    G->Edge[i][j] = Infinity;//将顶点与其他顶点的权值初始化为Infinity,表示目前两点无边
   }
  }
 }
 for (int i = 0; i < G->numV; i++)//将顶点信息按照输入顺序存入到顶点数组中
 {
  printf("请输入第%d个顶点的值:", i + 1);
  cin>>G->Vexs[i];
 }
 cout<<endl;
 for (int k = 0; k < G->numE; ++k)
 {
  int i, j;
  cout<<"请输入第"<<k+1<<"边的信息(顶点 顶点 权值):";
  cin>>vi>>vj>>e;
  /*
  若为不带权值的图,输入时e输入1,
  若为带权值的图,则e输入对应的权值
  区分带权图和不带权图
  */
  for (i = 0; i < G->numV; i++)
  {
   if (G->Vexs[i] == vi)
   {
    break;//找出顶点在数组中的位置
   }
  }
  for (j = 0; j < G->numV; j++)
  {
   if (G->Vexs[j] == vj)
   {
    break;
   }
  }
   //无向图的邻接矩阵具有对称性,也就是两点边的权值是一样的
  //有向图不具备此性质,只需要执行第一步,第二步不需要执行
  G->Edge[i][j] = e;
  G->Edge[j][i] = e;
}

//输出图的邻接矩阵
void ShowpGraph(MGraph G)
{
 printf("\n邻接矩阵为:\n");
 printf("\t");//规划格式
 for (int i = 0; i < G.numV; ++i)
 {
  printf("%6c", G.Vexs[i]);//显示每个顶点的值,并规划格式
 }
 printf("\n");
 for (int i = 0; i < G.numV; ++i)
 {
  printf("\n%8c", G.Vexs[i]);
  for (int j = 0; j < G.numV; ++j)
  {
   if (G.Edge[i][j] == Infinity)
   {
      printf("%6s", "∞");//两点之间无连接时输出“∞”
   }
   else
   {
      printf("%6d", G.Edge[i][j]); //两点之间有连接时,输出边的权值
   }
  }
  printf("\n");
 }
}

int main()
{
 MGraph G;
 CreateMGraph(&G);//创建图的邻接矩阵
 ShowpGraph(G);//输出图的邻接矩阵
 return 0;
}

从以上代码的注释中就可以看出以上代码可以创建多种类的图,上面只是创建无向带权图,可以尝试修改代码创建其它种类的图。

使用的图为:
在这里插入图片描述
运行结果如下:
在这里插入图片描述

邻接表

邻接表结合顺序存储和链式存储结构,链表代替邻接矩阵的每行,每个链表中存储顶点和该顶点相邻的所有点。对于带权图,在结点结构中增加权值域。本文实现无向图的邻接表创建,有向图在代码注释中会提示怎么创建,至于带权图根据上面所说增加权值域的方法来扩展吧。

#include <iostream>
using namespace std;

typedef struct EdgeNode
{
    int adjvex;//结点编号
    struct EdgeNode *next;//指向下一结点的指针
}EdgeNode;               //边结点

typedef struct VextexNode
{
    char vextexinfo;     //头结点信息(如A,B,C,D)
    EdgeNode *firstedge; //指向该结点的临边
}AdjList;                //头结点(即邻接表)

typedef struct
{
    int vexNum;                //顶点数
    int edgeNum;               //边数
    AdjList adjlist[20]; //头结点数组
}ALGraph;

void InitAdjList(ALGraph &g)//初始化邻接表
{
 int a,b;
 cout<<"输入图的顶点数和边数:";
 cin>>a>>b;
    g.vexNum=a;
    g.edgeNum=b;
    //初始化头结点数组(即表头信息)
    for(int i =1; i<=a ; i++)
    {
        cout<<"请输入第"<<i<<"个结点的信息:";
        cin>>g.adjlist[i].vextexinfo;
        g.adjlist[i].firstedge=NULL; //初始化指针指向空
    }
}

void Locata(ALGraph &g,char &vex1,char &vex2, int &m, int &n)//确定邻接表数组下标的位置
{
    for(int i =1;i<=g.vexNum;i++)
    {
        if(vex1 == g.adjlist[i].vextexinfo)
            m=i;
        if(vex2 == g.adjlist[i].vextexinfo)
            n=i;
    }
}//通过两个结点来确认边,并得到这两个结点的在数组的下标位置

void SetAdjList(ALGraph &g)//*建立邻接表
{
    char vex1,vex2;
    int m,n;
    for(int i=1;i<=g.edgeNum;i++) //有几条边就循环几次
    {
         cout<<"请输入第"<<i<<"条边(形如A B,表示A到B的一条边):";
         cin>>vex1>>vex2; //输入边的信息(即通过输入两顶点来确认边)
         Locata(g,vex1,vex2,m,n);//得到vex1和vex2在邻接表的数组下标位置m和n
         EdgeNode *s1 = new EdgeNode;
         s1->adjvex=n;   //无向图现在m是头,n是尾
         s1->next = g.adjlist[m].firstedge;
         g.adjlist[m].firstedge=s1;
         //要是将其改为有向图的邻接表,则下面这段代码全部删除
         EdgeNode *s2 = new EdgeNode;
         s2->adjvex=m;   //无向图现在n是头,m是尾
         s2->next = g.adjlist[n].firstedge;
         g.adjlist[n].firstedge=s2;
    }
}

void ShowAdjList(ALGraph &g)//显示邻接表
{
    cout<<"该图的邻接表如下:"<<endl;
    for(int i=1;i<=g.vexNum ;i++)
    {
        EdgeNode *p;
        cout<<"结点"<<i<<":";
        for(p = g.adjlist[i].firstedge; p!=NULL ; p=p->next)//循环到没有点与头结点相连
            cout<<p->adjvex<<"  ";
        cout<<endl;
    }
}

int main()
{
    ALGraph g;
    InitAdjList(g);
    SetAdjList(g);
    ShowAdjList(g);
    return 0;
}

使用的图是上面的图,只是边上无权值。

运行结果如下:
在这里插入图片描述

遍历DFS

以上代码虽能实现存储结构的显示,但我们依旧要学会图的遍历算法。深度优先搜索(DFS)首先访问图中某个顶点V,然后选择一个与V邻接的未被访问的顶点W,从W开始进行DFS,重复这个过程,直到一个顶点的所有邻接点全被访问过,按照刚才的访问顺序回溯到顶点的某个相邻结点U未被访问过为止,然后再从U出发进行DFS遍历。重复以上过程,直到所有的点都被访问过为止。具体实现看下面的代码。

邻接矩阵DFS算法

void dfs(MGraph* G, int* v, int s)
{
 int j;
 v[s] = 1;//表示结点被访问过
 cout << G->Vexs[s] << " ";
 for (j = 0; j < G->numV; j++)
 {
  if (G->Edge[s][j] != Infinity && !v[j])
   dfs(G, v, j);
 }
}

void DFS(MGraph* G)
{
 int i;
 int* v = (int*)malloc(sizeof(int) * G->numV);//用于记录图中的哪些结点被访问过了
 cout << endl << "图的邻接矩阵DFS遍历:" << endl;
 for (i = 0; i < G->numV; i++)//初始化结点都未被访问过
  v[i] = 0;
 for (i = 0; i < G->numV; i++)
 {
  if (!v[i])//对未被访问过的结点调用dfs函数,若是连通图,只会执行一次
   dfs(G, v, i);
 }
}

//只需在主函数调用DFS函数就可以实现对上面图的遍历了
int main()
{
 MGraph G;
 CreateMGraph(&G);//创建图的邻接矩阵
 ShowpGraph(G);//输出图的邻接矩阵
 DFS(&G);//图的邻接矩阵DFS算法输出
 cout << endl;
 return 0;
}

使用的图是上面的图。

运行结果如下:
在这里插入图片描述

邻接表DFS算法

void dfs(ALGraph &g,int* v,int s)
{
 EdgeNode *t=NULL;
 v[s]=1;
 cout<<s<<" ";
 t=g.adjlist[s].firstedge->next;
 while(t!=NULL)
 {
  if(!v[t->adjvex])
  {
   dfs(g,v,t->adjvex);
  }
  t=t->next;
 }
}

void DFS(ALGraph &g)
{
 int i;
 int* v=(int*)malloc(sizeof(int)*g.vexNum);//用于记录图中的哪些结点被访问过了
 cout<<endl<<"图的邻接表的DFS遍历:"<<endl;
 for(i=1;i<=g.vexNum;i++)//初始化结点都未被访问过
  v[i]=0;
 for(i=1;i<=g.vexNum;i++)
 {
  if(!v[i])//对未被访问过的结点调用dfs函数,若是连通图,只会执行一次
   dfs(g,v,i);
 }
}

//只需在主函数调用DFS函数就可以实现对上面图的遍历了
int main()
{
    ALGraph g;
    InitAdjList(g);
    SetAdjList(g);
    ShowAdjList(g);
    DFS(g);//图的邻接表DFS算法输出
    cout<<endl;
    return 0;
}

使用的图是上面的图,只是边上无权值。

运行结果如下:
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值