本人最近学习了图论的基础知识,做了几道入门题目,慢慢的从图论的创建、存储过渡到图的遍历,求图的单源最短路径、拓扑排序。
本次先介绍几种存储图的信息的方法
图的存储
1.邻接表存储
假设我们现在有一个图里面有n个点,m条边,要利用数组存储的话,我们需要定义一个n*n的二维数组,int edge[n+1][n+1],因为数组下标从0开始,为了方便处理边的信息多定义一行一列
假设图中有以上信息,我们要利用邻接矩阵存储
edge[1][2]=1;
edge[2][3]=1;//有向边
edge[1][2]=1;
edge[2][1]=1;
edge[2][3]=1;
edge[3][2]=1;//无向边,一条边需要双向存储
图中有节点1指向节点2的边和节点2到节点3的边,它们都是有向的,如果是无向的边,还需要给对称的变量赋值为1代表存在该边。
通过这样处理,图中的信息就被存储下来了,二维数组的行列下标分别表示起始点和终止点,对应的变量存储一表示存在该边,为0表示不存在。
如果是带权的图的存储,将上述存储中的1替换成相应的权值即可,同样通过对应下标变量是否为0来判断是否存在改变,非0变量的值就是这个边的权值。
例如上图信息,利用邻接矩阵存储代码如下:
edge[1][2]=2;
edge[2][3]=3;
这种方式存储图是不是觉得很简单?要查询某一条边是否存在只需要看相应的二维数组变量是不是为0,查询效率为O(1),但是一个东西有利就一定会有弊,想上图中的势力,我们要用邻接矩阵存储就需要定义一个大小为4*4二维数组(下标为0的不用),然而要存储边的信息我们只用到了两个内存单元,这就浪费了大量的内存空间,属于是以空间换时间了。
2.C++ vector存储
在上面我们知道了利用数组存储图有时候会造成大量的空间浪费,恰好在C++中引入了向量这一类容器,它会随着你放入的内容自动适配存储空间,我们可以利用它来代替二维数组,下面介绍用法
在二维数组中,我们是利用数组下标直接对应点和点的关系,例如edge[1][2]就存储着点1和点2的关系,这里我们定义一个 vector<int> 数组,根据图中点的数量确定数组的大小,每个数组存储对应下标的邻接点的信息,下面给出示例
我们该如何利用vector存储上述图的信息呢?
首先一共四个点,定义大小为4的vector<int> 数组
vector<int> edge[5];//这里默认下标为0的空间不使用
//下标为i的向量中存储与i相连的点
//向量i中每个变量x都代表一条i到x的边
//图中有以下边
//1->3 3->2 3->4
edge[1].push_back(3);
edge[3].push_back(2);
edge[3].push_back(4);
这样,整个向量数组所占空间大小就是3*sizeof(int)个数组
edge中实际只存储如上信息,edge[2]和edge[4]中由于没有内容,系统没有给分配内存,这样只占用有限的空间就存储好了一个图的信息。
那么如果我要查询与节点2相连的节点分别是哪些怎么找呢?代码如下:
vector<int> loca;//存储与结点2相连的点
for(int i=0;i<edge[2].size();i++)
{
loca.push_back(edge[2][i]);
}
利用如上代码就可以查找一个点所相连的所有节点。
上面是无权边,那么有权的边利用这个方法该怎么存呢?
这个时候我们就需要新定义一个向量数组,与存储边的方式类似,我们存储权实际上就是复刻一个edge向量数组,然后在每一个终点对应的位置替换成相应的边的权值。
来个简单的,图中有1,2这两个点 一个节点1指向节点2的边,权值为5
vector<int> edge[3];//存储边
vector<int> wi[3];//存储权值
edge[1].push_back(2);//将边的信息存入edge
wi[1].push_back(5);//将权值存入对应的wi向量数组中的位置
总结
总的来说,图的存储,如果这个图是稠密图的话,就可以选择邻接矩阵存储,这样简单粗暴,用起来也简单
如果不确定边的数量,我们就可以选用vector存储,这样不会超内存,除了本章介绍的两种方式以外还有很多存储图的方式,这两种比较适合新手入门使用,慢慢的熟练之后就可以选择其他的方式。