ps:写这篇博客的原因是因为在一次比赛中需要按字典序来遍历图,就是这样一道简单的题,由于本蒟蒻只会链式前向星存图方式导致一直做不出来,深切感受到了来自图论的恶意!!!
三种常见的存图方式;
一、邻接矩阵
邻接矩阵是使用二维的数组来实现存图方式,用于储存稠密图,第一维表示每一个点,第二维表示以这个点为出点的边,如果没有这条边则标记为无穷大。
1 //初始化
2 void init()
3 {
4 memset(g, 0x3f, sizeof g);
5 }
6
7 //插入边
8 void add(int a, int b, int c)
9 {
10 g[a][b] = c;
11 g[b][a] = c;//无向图需要建立一个反边
12 }
13
14 //查询操作
15 int query(int a, int b)
16 {
17 return g[a][b];
18 }
优点:
易于实现,且对于储存稠密图有优势
缺点:
数组开销过大,浪费空间,当储存稀疏图时无效边过多
二、邻接表
对于每一个点使用不定长度的数组来储存从该点出发的所有边的情况。
1 struct it//定义一个结构体
2 {
3 int e, w;
4 };
5
6 vector<it> edge[N];//建立一个结构体类型的二维动态数组,若无权重可以不定义结构体直接使用int
7
8 //初始化
9 int init(int x)
10 {
11 edge[x].clear();//清除以x为起点的所有边
12 }
13
14 //插入操作
15 int add(int a, int b, int w)
16 {
17 edge[a].push_back(it {b, w});//将出点和权重加入
18 }
优点;
较为简单易学:数组转链表,定长转不定长
内存利用率较高:对于顶点数V、边数E,空间复杂度为O(V + E),能较好的处理稀疏图
对不确定的边操作比较方便:比如,要遍历从某点出发的某条边,不会像邻接矩阵一样可能遍历到不存在的边
缺点
重边不好处理:判重比较麻烦,还要遍历已有的边,不能直接判断;一般情况下使用邻接表存图是会存储重边的,不会做重边的判断
三、链式前向星
使用多个开辟的数组实现套娃来存图
h数组表示当前插入边的头结点,e数组表示插入边的出点,ne数组表示插入边的下一个头结点,w数组表示权重,整个的链表是由idx(表示当前插入的是第几条边)来实现串联。
1 void insert(int a,int b,int c)
2 {
3 e[idx] = b;
4 w[idx] = c;//w储存每条边的权值,dist储存初始节点到每一个节点的最短距离
5 ne[idx] = h[a];
6 h[a] = idx ++ ;
7 }
优点:
内存利用率高:相比vector
实现的邻接表而言,可以准确开辟最多边数的内存,不像vector
实现的邻接表有爆内存的风险
对不确定的边操作比较方便:和邻接表一样不会遍历到不存在的边
缺点
操作复杂一点:要求比较熟练,不熟练写起来比较慢
重边不好处理:这点与邻接表一样,只有通过遍历判重
对确定边操作效率不高:也与邻接表一样,不能通过两点马上确定边,只能遍历查找