今年暑假入坑了ACM,可以说开始的很晚了。平时看到各位大佬们的博客都写的很是精彩了,也想通过写博客来督促一下自己,总结一下自己的每天所学和一些感受吧,希望能一直坚持下去,这条路注定不会好走,那就坦然去接受一场场爆零,从零开始慢慢积累吧。(刚开始入坑,写的内容可能会有很多很多的问题,希望大家多多指出,一起进步。)
本来是最近在准备学习网络流,但是发现,基础差到模板里建图就有些不会,于是先来学学习如果存图,大概有三种这样的方式:
一、邻接矩阵存图
顾名思义就是用矩阵来记录一个图,矩阵中第 i 行第 j 列的值就表示顶点 i 到顶点 j 的权值。
#include<bits/stdc++.h> const int V = 1000; //最大顶点数 int mat[maxn][maxn];//开设二维数组来存矩阵 int main() { int i,j,w; memset(mat, 0, sizeof(mat)); //初始化操作假设权值为0表示没有该边 scanf("%d%d%d",&i,&j,&w);//输入边的信息:起点、终点、权重 mat[i][j] = w;// //增加顶点i到顶点j的权值为w的边 mat[i][j] = 0;//删除边 printf("%d",mat[i][j]);//查询边 }
优点:容易理解,如果矩阵已经确定,可在O(1)的时间复杂度实现添加,修改,删除的操作
缺点:空间复杂度过高,并且极为致命,一旦顶点数过多,需要利用空间就很大,而当图很稀疏时候,就会造成内存的浪费
二、邻接表
邻接表存储,又叫链式存储。这里主要说一下用数组的方式来模拟建立邻接表,特别是在网络流应用中,要建立好多好多数组,分别存取边的起点、终点、特别是还要存权重的数组域。具体解释看代码:
#include<bits/stdc++.h> #define maxn 1000001 #define INF 119260817 using namespace std; int cnt,cost[maxn],from[maxn],to[maxn],Next[maxn], head[maxn]; int n,m; void add(int x,int y,int z)//建边 { ++cnt; cost[cnt]=z;//权重 from[cnt]=x;起点 to[cnt]=y;//汇点 Next[cnt]=head[x];//存边表,存储上个边的号head[x],形成链表 head[x]=cnt;//记录每个起点的相连的一个点,就是所在链表的起点 } int main() { cnt=1; memset(head,-1,sizeof(head)); scanf("%d%d",&n,&m);//输入n个顶点,m条边 for(int i=1;i<=m;i++){ int x,y,z; scanf("%d%d%d",&x,&y,&z);//起点、终点、权重 add(x,y,z);add(y,x,0); //刚开始要建反向边,容量是0 } }
由代码可知:此方法时间、空间复杂度都是O(m),遍历一条链表时复杂度也为O(m),远小于邻接矩阵中的O(n2)。
三、链式向前星
感觉和邻接表存储有点相似,通过结构体数组来存储边的终点、上一个边、权重(因为一个结点同一起点所以不需要存起点了),再加一个head数组,head数组来存边的序号。
const int maxn = 10005; //点的最大个数 int head[maxn], cnt=0;//head数组用来表示以i为起点的一条边存储的位置 //cnt用来记录序号 struct Edge { int to; //此边的终点 int w; //此边的权重 int next; //同一起点的上一条边的储存位置 }Edge edge[maxn]; void add(int u,int v,int w) //初始化加边 { edge[cnt].w = w;//记录边的权重 edge[cnt].to = v;//记录边的终点 edge[cnt].next = head[u];//存储同一起点的上一条边的位置 head[u] = cnt++;//给边赋予序号 } int main() //遍历所有链表 { for(int i=0; i<=n; i++) for(int j=head[i]; j!=-1; j=edge[j].next) {......}//具体操作省略 }
与上面邻接矩阵相比,最大缺点就是,一开始不容易理解了,需要先看下数据结构链表的东西,这个方法很节省内存,效率高。
这就是三种存图的方式,学会了这三种就可以进一步学习图论的一些算法了。
要一直坚持下去哦!
原文地址:https://www.cnblogs.com/dreamxy/p/11251628.html