说起图的储存就先要说到变的储存了!
说起边有些童鞋恐怕还分不清楚什么和什么吧!
好,下面我们就来说一下边吧!
一。边的分类
边可以通过有无方向分为
1.有向边
2.无向边
大都数情况下我们可以把无向边看作是两条有向边
边通过有无权值分为
1.带权边
2.无权边
大多数情况下我们把无权边看作是边权为1的带权边
说完边的分类,那我们下面就要说到边的储存了!
二。边的储存
边有以下几种储存方式:
1.邻接矩阵(本人认为这是最好写的的一种了!)
优点:实现简便,运用位运算和矩阵算法优化的时候更方便
缺点:时空复杂度较大
2.链表储存
优点:时空复杂度较小
缺点:实现相对复杂
3.边集数组
优点:容易对边进行处理 ,对边进行排序,方便整体处理
缺点:难以对图进行遍历,难以求出每个节点相邻的边的相邻节点
邻接矩阵
对于一个给定的图,有n个点m条边,那我们若要储存这个图那就要弄一个n*m的邻接矩阵
注意:
① 在简单应用中,可直接用二维数组作为图的邻接矩阵(顶点表及顶点数等均可省略)。
② 当邻接矩阵中的元素仅表示相应的边是否存在时,EdgeTyPe可定义为值为0和1的枚举类型。
③无向图的邻接矩阵是对称矩阵,对规模特大的邻接矩阵可压缩存储。
④邻接矩阵表示法的空间复杂度S(n)=0(n 2 )。
⑤建立无向网络的算法。
代码:
#include<stdio.h> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 10000 using namespace std; int e[N][N],vis[N]; int n,m,a,b,c; void bfs(int x) { vis[x]=1; for(int i=1;i<=n;i++) { if(e[x][i]&&!vis[i]) bfs(i); } } int main() { scanf("%d%d",&n,&m);//n个点,m条边 for(int i=1;i<=m;i++) { scanf("%d%d%d",&a,&b,&c); e[a][b]=c;//有向边 } bfs(1); return 0; }
时空复杂度均为o(n^2)!
邻接表
对于图来说,邻接矩阵村边是一个很不错的选择!但是对于点数十分稀疏的图来说,这种方法是十分浪费空间的!!
so,我们就有了另外一种储存边的方法——邻接表!
那么你肯定会问邻接表是什么鬼,那是怎么用的??
邻接表的处理方法
1、图中顶点用一个一维数组存储,另外,对于顶点数组中,每个数据元素还需要存储指向第一个邻接点的指针,以便于查找该顶点的边信息。
2、图中每个顶点vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以用单链表存储,无向图称为顶点vi的边表,有向图称为顶点vi作为弧尾的出边表
代码
#include<stdio.h> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 10000 #define oo 123456 using namespace std; int head[N],n,m,a,b,c,num; bool vis[N]; struct Edge { int next;//下一条边的编号 int to;//这条边到达的点 int dis;//这条边的长度 }edge[N]; void ins(int from,int to,int dis) { edge[++num].next=head[from]; edge[num].to=to; edge[num].dis=dis; head[from]=num; } int dfs(int x) { vis[head[x]]=1; for(int i=1;i<=n;i++) { if(!vis[head[i]]) dfs(i); } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=n;i++) { scanf("%d%d%d",&a,&b,&c); ins(a,b,c); } dfs(1); return 0; }
vector
啊啊啊!这是些什么鬼!有没有感觉前面这些东西太恶心了!完全记不住啊!
好,那我们下面来弄个简单的!
stl中的vector!
vector可以看做一个不用人为开大小的一个数组,我们可以对每一个节点都使用一个vector,然后把每个节点的信息都储存在这个点的vector中即可。
对于一个有边权的图来说,有pair讲点的编号和权值结合起来储存在vector中
代码
#include<vector> #include<cstdlib> #include<cstring> #include<stdio.h> #include<iostream> #include<algorithm> #define N 100000 #define maxn 123456 using namespace std; int m,n,x,y,z; bool vis[N]; vector<pair<int,int> >vec[N]; void bfs(int x) { vis[x]=1; for(int i=0;i<vec[x].size();i++) { if(!vis[vec[x][i].first]) bfs(vec[x][i].first); } } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { scanf("%d%d%d",&x,&y,&z); vec[x].push_back(make_pair(y,z)); } bfs(1); return 0; }
边集数组
边集数组就是将输入的边用一个数组保存下来,而不是将其与点放在一起。
这种方法一般是在不需要点的信息时使用,不要遍历整张图,只需要每个边的信息时使用,如并查集。
当然也存在一种算法可以在使用边集数组时,十分迅速的将点的信息求出。
这种方法就是向前星。
我们可以再输入边的时候将边按照出点的顺序进行排序,这样每一个点的所有出边都是连续的。
扫一遍整张图后,我们就可以知道每一个点的所有出边所在的区间。
对于无向边我们就要存两遍!
由于要对给出的每一条边进行排序,所以时空复杂度较大。
#include<cstdio> #include<cstdlib> #include<cstring> #include<iostream> #include<algorithm> #define N 100000 #define maxn 123456 using namespace std; int n,m,a,b,c,len,l[N],r[N]; bool vis[N]; struct Edge { int x,y,z; }edge[N]; int cmp(Edge a,Edge b) { return a.x<b.x; } void dfs(int x) { vis[x]=1; for(int i=l[x];i<r[x];i++) if(!vis[edge[i].y]) dfs(edge[i].y); } int main() { scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { scanf("%d%d%d",&a,&b,&c); edge[++len].x=a; edge[len].y=b; edge[len].z=c; } sort(edge+1,edge+1+m,cmp); for(int i=1;i<=m;i++) if(edge[i].x!=edge[i-1].x) l[edge[i].x]=r[edge[i-1].x]=i; dfs(1); }