图的存储结构(邻接矩阵和邻接表)

这是我参与11月更文挑战的第22天,活动详情查看:2021最后一次更文挑战

前言:

前面我们学习图的有些定义和术语,对图这个数据结构有了新的见解和认知,让我们理解图结构的知识,今天我们学习图的存储结构,图的存储结构比较多,我们今天主要是学习邻接矩阵和邻接表,对于邻接矩阵是使用数组的形式,我们不叫顺序存储结构了,叫邻接矩阵,邻接表和我们十字链有异曲同工之处,只不过邻接表的链表要多一点,好了话不多说我们开始学习吧!!!

每日一遍,防止颓废

image-20211122113601095

1.邻接矩阵

官方术语:

邻接矩阵(Adjacency Matrix)是表示顶点之间相邻关系的矩阵。设G=(V,E)是一个图,其中V={v1,v2,…,vn} [1]  。G的邻接矩阵是一个具有下列性质的n阶方阵:
①对无向图而言,邻接矩阵一定是对称的,而且主对角线一定为零(在此仅讨论无向简单图),副对角线不一定为0,有向图则不一定如此。
②在无向图中,任一顶点i的度为第i列(或第i行)所有非零元素的个数,在有向图中顶点i的出度为第i行所有非零元素的个数,而入度为第i列所有非零元素的个数。
③用邻接矩阵法表示图共需要n^2个空间,由于无向图的邻接矩阵一定具有对称关系,所以扣除对角线为零外,仅需要存储上三角形或下三角形的数据即可,因此仅需要n(n-1)/2个空间。
复制代码

讲人话就是:邻接矩阵和矩阵的三元组表示法有点相似,就是一个顶点,和一个二维数组组合,二维数组的第一个下标表示边的起点,第二下标表示边的终点,有边把这个位置的值赋值为1,没有就赋值为0如下图:.

image-20211121225924966

1.1图的邻接矩阵类型声明:

#include <stdio.h>
#include <malloc.h>
#define MAXVEX 100	  		 //图中最大顶点个数
#define INF 32767				//表示∞
typedef char VertexType[10]; 		//定义VertexType为字符串类型
typedef struct vertex
{  int adjvex; //顶点编号
   VertexType data; 		//顶点的信息
} VType;		//顶点类型
typedef struct graph
{  int n,e;			//n为实际顶点数,e为实际边数
   VType vexs[MAXVEX];		//顶点集合
   int edges[MAXVEX][MAXVEX]; 		//边的集合
}  MatGraph;		//图的邻接矩阵类型
复制代码

1.2 建立图的邻接矩阵运算算法

由邻接矩阵数组A、顶点数n和边数e建立图G的邻接矩阵存储结构。

void CreateGraph(MatGraph &g,int A[][MAXVEX],int n,int e)
{  
	int i,j;
    g.n=n; g.e=e;
    for (i=0;i<n;i++)
    for (j=0;j<n;j++)
    g.edges[i][j]=A[i][j];
}
复制代码

1.3销毁图运算算法

这里邻接矩阵是图的一种顺序存储结构,其内存空间是由系统自动分配的,在不再需要时由系统自动释放其空间。所以对应的函数不含任何语句。

void DestroyGraph(MatGraph g)//内存空间是由系统自动分配的,直接关闭程序就释放了
{  }
复制代码

1.4 输出图运算算法

将图G的邻接矩阵存储结构输出到屏幕上

void DispGraph(MatGraph g)
{  int i,j;
   for (i=0;i<g.n;i++)
   {  for (j=0;j<g.n;j++)
        if (g.edges[i][j]<INF)
           printf("%4d",g.edges[i][j]);
        else
           printf("%4s","∞");
      printf("\n");
   }
}
复制代码

1.5求顶点度运算算法

对于无向图和有向图,求顶点度有所不同。依据定义,求无向图G中顶点v的度的算法如下:

int Degree1(MatGraph g,int v)	 //求无向图中顶点的度
{  int i,d=0;
   if (v<0 || v>=g.n)
      return -1;		 //顶点编号错误返回-1
   for (i=0;i<g.n;i++)
      if (g.edges[v][i]>0 && g.edges[v][i]<INF)
	 d++;		//统计第v行既不为0也不为∞的边数即度
   return d;
}

int Degree2(MatGraph g,int v)	 //求有向图中顶点的度
{  int i,d1=0,d2=0,d;
   if (v<0 || v>=g.n)
      return -1;	//顶点编号错误返回-1
   for (i=0;i<g.n;i++)
      if (g.edges[v][i]>0 && g.edges[v][i]<INF)
	 d1++;		//统计第v行既不为0也不为∞的边数即出度
   for (i=0;i<g.n;i++)
      if (g.edges[i][v]>0 && g.edges[i][v]<INF)
	 d2++;		//统计第v列既不为0也不为∞的边数即入度
   d=d1+d2;
   return d;
}

复制代码

1.6 主函数及效果展示

int main(int argc, char** argv)
{
	MatGraph g;
	int n=5,e=7,i;
	int A[MAXVEX][MAXVEX]={{0,1,2,6,INF},{INF,0,INF,4,5},{INF,INF,0,INF,3},{INF,INF,INF,0,INF},{INF,INF,INF,7,0}};
	CreateGraph(g,A,n,e);	//建立图7.4图的邻接矩阵
	printf("图G的存储结构:\n"); DispGraph(g);
	printf("图G中所有顶点的度:\n"); 
	printf("  顶点\t度\n");
	for (i=0;i<g.n;i++)
		printf("   %d\t%d\n",i,Degree2(g,i));
	DestroyGraph(g);
	return 0;
}
复制代码

image-20211122111847765

2.邻接表

邻接表是图的一种链式存储结构。在邻接表中,对图中每个顶点建立一个带头结点的单链表,把该顶点的所有相邻点串起来。所有的头结点构成一个数组,称为头结点数组,用adjlist表示,第i个单链表adjlist[i]中的结点表示依附于顶点i的边,也就是说头结点数组元素的下标与顶点编号一致。
复制代码

讲人话:就是有一个数组类型的单链表,这个数组的每个结点都代表一个头指针,这个头指针再指向一个单链表,这个单链表用来保存下一个你要指向的顶点(也就是头指针)和权值,对权值不知道的同学看博主上一篇文章

image-20211121234839848

2.1图的邻接表存储结构的类型声明如下:

#include <stdio.h>
#include <malloc.h>
#define MAXVEX 100				//图中最大顶点个数
#define INF 32767				//表示∞
typedef char VertexType[10];	  //VertexType为字符串类型
typedef struct edgenode
{  int adjvex;			  //相邻点序号
   int weight;			  //边的权值
   struct edgenode *nextarc;	  //下一条边的顶点
} ArcNode;		//每个顶点建立的单链表中边结点的类型
typedef struct vexnode
{  VertexType data;		  //存放一个顶点的信息
   ArcNode *firstarc; 	  //指向第一条边结点
} VHeadNode;			  //单链表的头结点类型
typedef struct 
{  int n,e;			  //n为实际顶点数,e为实际边数
   VHeadNode adjlist[MAXVEX];	  //单链表头结点数组
} AdjGraph;			  //图的邻接表类型

复制代码

2.2 邻接表的创建

image-20211122110615126

void CreateGraph(AdjGraph *&G,int A[][MAXVEX],int n,int e)
{  int i,j;
   ArcNode *p;
   G=(AdjGraph *)malloc(sizeof(AdjGraph));
   G->n=n; G->e=e;
   for (i=0;i<G->n;i++)	//邻接表中所有头结点的指针域置空
     G->adjlist[i].firstarc=NULL;
   for (i=0;i<G->n;i++)	//检查A中每个元素
     for (j=G->n-1;j>=0;j--)
        if (A[i][j]>0 && A[i][j]<INF)	      //存在一条边
        {  p=(ArcNode *)malloc(sizeof(ArcNode));  //创建结点p
           p->adjvex=j;
           p->weight=A[i][j];
           p->nextarc=G->adjlist[i].firstarc;  //头插法插入p
           G->adjlist[i].firstarc=p;
        }
}
复制代码

2.3销毁图运算算法

邻接表的头结点和边结点都是采用malloc函数分配的,在不再需要时应用free函数释放所有分配的空间。 基本思路:通过adjlist数组遍历每个单链表,释放所有的边结点,最后释放adjlist数组的空间。

void DestroyGraph(AdjGraph *&G)	//销毁图
{  int i;
   ArcNode *pre,*p;
   for (i=0;i<G->n;i++)		//遍历所有的头结点
   {  pre=G->adjlist[i].firstarc;
      if (pre!=NULL)
      {  p=pre->nextarc;
         while (p!=NULL)  //释放adjlist[i]的所有边结点
         {  free(pre);
            pre=p; p=p->nextarc;
	 }
	 free(pre);
      }
   }
   free(G);		   //释放G所指的头结点数组的内存空间
}

复制代码

2.4输出图运算算法

将图G的邻接表存储结构输出到屏幕上。

void DispGraph(AdjGraph *G)		//输出图的邻接表
{  ArcNode *p;
   int i;
   for (i=0;i<G->n;i++)		//遍历所有的头结点
   {	printf("  [%2d]",i);
	p=G->adjlist[i].firstarc;	//p指向第一个相邻点	
	if (p!=NULL)
          printf(" →");
	while (p!=NULL)
	{  printf(" %d(%d)",p->adjvex,p->weight);
          p=p->nextarc;		//p移向下一个相邻点
	}
	printf("\n");
  }
}

复制代码

2.5求顶点度运算算法

对于无向图和有向图,求顶点度有所不同。依据定义,求无向图G中顶点v的度的算法如下:

int Degree1(AdjGraph *G,int v)   //求无向图G中顶点v的度
{  int d=0;
   ArcNode *p;
   if (v<0 || v>=G->n)
      return -1;	//顶点编号错误返回-1
   p=G->adjlist[v].firstarc;
   while (p!=NULL)	//统计v顶点的单链表中边结点个数即度
   {  d++;
      p=p->nextarc;
   }
   return d;
}

int Degree2(AdjGraph *G,int v)  //求有向图G中顶点v的度
{  int i,d1=0,d2=0,d; ArcNode *p;
   if (v<0 || v>=G->n)
     return -1;		   //顶点编号错误返回-1
   p=G->adjlist[v].firstarc;
   while (p!=NULL)	   //统计v的单链表中边结点个数即出度
   {  d1++;
      p=p->nextarc;
   }
   for (i=0;i<G->n;i++)  //统计边结点中adjvex为v的个数即入度
   {  p=G->adjlist[i].firstarc;
      while (p!=NULL)
      {  if (p->adjvex==v) d2++;
	 p=p->nextarc;
      }
   }
   d=d1+d2;
   return d;
}

复制代码

2.6 主函数

int main(int argc, char** argv)
{
	AdjGraph *G;
	int n=5,e=7,i;
	int A[MAXVEX][MAXVEX]={{0,1,2,6,INF},{INF,0,INF,4,5},{INF,INF,0,INF,3},{INF,INF,INF,0,INF},{INF,INF,INF,7,0}};
	CreateGraph(G,A,n,e);	//建立图7.4图的邻接表
	printf("图G的存储结构:\n"); DispGraph(G);
	printf("图G中所有顶点的度:\n"); 
	printf("  顶点\t度\n");
	for (i=0;i<G->n;i++)
		printf("   %d\t%d\n",i,Degree2(G,i));
	DestroyGraph(G);
	return 0;
}
复制代码

时间关系博主就不展示效果了,把这些代码复制过去就可以了

总结:

图的存储我们学习的差不多了,邻接矩阵就是一个存放顶点的数组和一个二维数组组成,二维数组用来下标用来表示顶点与顶点的边连接,有这条边就把这两个下标对于的数组赋值1,没有赋值0,邻接表就是有多个链表,一个用来保存头结点,每个头结点会指向一个链表,这个链表用来存顶点的数组的下标,**注:存头结点的链表是结构体数组类型的。**好了这篇文章博主就学完了,传作不易,点赞关注评论收藏,谢谢啦!!!

1235468910

猜你喜欢

转载自juejin.im/post/7033234846256218143