图的存储结构(邻接矩阵和邻接表)
前言:
前面我们学习图的有些定义和术语,对图这个数据结构有了新的见解和认知,让我们理解图结构的知识,今天我们学习图的存储结构,图的存储结构比较多,我们今天主要是学习邻接矩阵和邻接表,对于邻接矩阵是使用数组的形式,我们不叫顺序存储结构了,叫邻接矩阵,邻接表和我们十字链有异曲同工之处,只不过邻接表的链表要多一点,好了话不多说我们开始学习吧!!!
每日一遍,防止颓废
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如下图:.
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;
}
2.邻接表
邻接表是图的一种链式存储结构。在邻接表中,对图中每个顶点建立一个带头结点的单链表,把该顶点的所有相邻点串起来。所有的头结点构成一个数组,称为头结点数组,用adjlist表示,第i个单链表adjlist[i]中的结点表示依附于顶点i的边,也就是说头结点数组元素的下标与顶点编号一致。
讲人话:就是有一个数组类型的单链表,这个数组的每个结点都代表一个头指针,这个头指针再指向一个单链表,这个单链表用来保存下一个你要指向的顶点(也就是头指针)和权值,对权值不知道的同学看博主上一篇文章
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 邻接表的创建
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,邻接表就是有多个链表,一个用来保存头结点,每个头结点会指向一个链表,这个链表用来存顶点的数组的下标,**注:存头结点的链表是结构体数组类型的。**好了这篇文章博主就学完了,传作不易,点赞关注评论收藏,谢谢啦!!!