在我的认识中学习一种新的数据结构要面临三个问题:1,这种数据结构的定义是什么。2,这种数据结构该如何储存。3,依靠这种数据结构可以处理什么问题。我将按照这三个步骤来阐明我是如何学习图的。
图的定义:图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,图通常表示为G(V,E),其中,G 表示一个图,V 是图 G 中顶点的集合,E 是图 G 中边的集合。
无向图:若i与j两顶点之间的连接边是没有方向的就称该边为无向边,若图中所有边都是无向边则该图为无向图。
有向图:若i与j两顶点之间的连接边是有方向的就称该边为有向边,若图中所有边都是有向边则该图为有向图。
权的定义:有些图的边或弧具有与它相关的数字,这种与图的边或弧相关的数叫做权。
度的定义:无向图顶点的边数叫度,有向图顶点的边数叫出度和入度。
附上一张简单的图的图片:
图的储存:说到图的储存问题,这是个困扰了我好几天的问题(本人是个蒟蒻),图的储存一般有两种方法:1,邻接矩阵,2,邻接表。接下来我将介绍这两种储存方法。
邻接矩阵:多数的讲解说邻接矩阵由两个数组组成,但是为了方便使用一般直接定义二维数组来储存,邻接矩阵其实很好理解,我们定义一个二维数组G[max][max],G[i][j]就表示i与j的连接状态,一般定为为0则没有连接边为1则有连接边,你也可以用G[i][j]来储存i与j连接边的权值,总而言之是用来储存一条边的信息。附上邻接矩阵储存与遍历的代码:
#include<stdio.h>
int G[101][101],n,m;//定义所需要的变量,n,m分别代表顶点的总数与边的总数
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int a,b;//辅助输入
scanf("%d%d",&a,&b);
G[a][b]=1;//表示a,b间有路
}
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
printf("%d ",G[i][j]);
}
printf("\n");
}//遍历打印邻接矩阵
return 0;
}
虽然邻接矩阵的实现十分简单方便但是我们不难发现它的弊端,邻接矩阵对空间的占用是巨大的,在解决问题时经常会爆空间(说多了都是泪),这时候我们就得使用另一种存储方式了,它就是邻接表。
邻接表:邻接表说白了就是储存图的链式结构,想必没有几个人在刷题时使用链表吧,所以我将介绍一种用数组模拟邻接表的数据结构:链式前向星。
首先先介绍一下我们所需要定义的变量:
struct data{
int to,next,w;
};
struct data edge[100000];
int head[10000],n,m,cnt;
我们先定义一个结构体data,其中的成员分别是to,next,w(用来储存权值可以不添加)。head[max]用于记录某点的第一条连接边,cnt作为下标无实际作用,n,m点与边的总数量。
接下来看看添加边的函数:
void add(int x,int y,int z){
edge[++cnt].to=y;
edge[cnt].w=z;
edge[cnt].next=head[x];
head[x]=cnt;
}
其实就是对链表的模拟,edge[++cnt].to用来表明cnt号边的终点(或者说连接的下一个点),edge[cnt].w=z;表示cnt号边的权值,edge[cnt].next表示cnt的下一条边的编号,如此这般便能将一条边的信息存入这几个数组,附上完整的储存与遍历代码:
#include<stdio.h>
struct data{
int to,next,w;
};
struct data edge[100000];
int check[10000],head[10000],cnt,dis[10000],n,m,s,inf=99999999;
void add(int x,int y,int z){
edge[++cnt].to=y;
edge[cnt].w=z;
edge[cnt].next=head[x];
head[x]=cnt;
}
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
}
for(int i = 1; i <= n; i++)
{
printf("%d\n",i);
for(int j = head[i]; j != 0; j = edge[j].next)
{
printf("%d %d %d\n",i,edge[j].to,edge[j].w);
}
printf("\n");
}
return 0;
}