ACM图论之存图方式

<转--剑紫青天>

对于ACM图论方面的题目总是免不了首先要建图存图,使用合适的存图方式不但是AC的必要条件,解题事半功倍。

以下主要分析三种常见的存图方式的优缺点以及代码实现

邻接矩阵

邻接矩阵是三种存图方式中最简单也最为暴力的一种存图方式了。

存图思想

使用一个矩阵来描述一个图,对于矩阵的第i行第j列的值,表示编号为i的顶点到编号为j的顶点的权值。

代码实现

对于邻接矩阵来说,它的代码实现都十分简单,二维数组就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include <string.h>

// 最大顶点数
const int V = 1000;

// 邻接矩阵的定义
// mat[i][j] 表示 顶点`i`到顶点`j`的权值

int mat[V][V];


// 邻接矩阵的初始化操作
// 假设权值为零表示没有该边

memset(mat, 0, sizeof(mat))


// 增加边
// 新增顶点`i`到顶点`j`的边,权值为`w`

mat[i][j] = w;


// 删除边
// 删除顶点`i`到顶点`j`的边

mat[i][j] = 0;


// 查询边
// 查询顶点`i`到顶点`j`的边权

mat[i][j];

优点

使用邻接矩阵来进行建图存图有以下优点

缺点

邻接矩阵存图虽然简单优雅,但是它的一些缺点却几乎是致命的。

邻接表

邻接表在三种常用的存图方式中属于较为中庸和普遍的存图方式了,缺点不致命,优点不明显。

存图思想

邻接矩阵对于每个顶点使用定长的数组来存储以该点出发的边的情况。第i个数组的第j个值存储的是从顶点i到顶点j的边的权值。
邻接表则是对于每个顶点使用不定长的链表来存储以该点出发的边的情况。因此对于第i个链表的第j个值实际上存储的是从编号为i的顶点出发的第j条边的情况。

一般来说,如果有边权的话,邻接表的链表存储的是一个结构体,这个结构体存储该边的终点以及边权。

下面给个邻接表与邻接矩阵存图的示例比较。

links&matrix.png links&matrix.png

代码实现

在ACM题目中,动态的数据结构一般是不被推荐的,因为动态开辟内存比较消耗时间,且写起来复杂容易出错。
大部分情况我们使用C++STL里的vector作为链表来实现图的邻接表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <vector>

using namespace std;

// 最大顶点数
const int V = 100000;


// vector实现的邻接表的定义
// 不考虑边权,存储类型为int型

vector<int> e[V];


// 邻接表的初始化操作
// 将起点为`i`的边链表全部清空

e[i].clear();


// 增加边
// 新增顶点`i`到顶点`j`的边

e[i].push_back(j);


// 查询边

e[i][0]; // 查询以`i`为起点的第一条边`i->e[i][0]`

for (int j=0; j<(int)e[i].size(); ++j) {
if (e[i][j] == k) { // 查询边`i->k`
// do something.
}
}

优点

缺点

链式前向星

链式前向星是前向星的升级版,因为它可以完美代替前向星,所以就跳过前向星的学习,直接学习链式前向星。

存图思想

这种存图方式的数据结构主要是边集数组,顾名思义,图的边是用数组来存储的
当然想要完美表示图结构,光有一个边集数组还不够,还要有一个数组存储指向每一个点的第一条边的“指针”。
而每一条边都需要存储接下来一条边的“指针”,这样就能够像类似邻接表一样方便遍历每一个点的所有边了。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
#include <string.h>

// 最大顶点数
const int V = 100000;
// 最大边数
const int E = 100000;

// 边结构体的定义
struct Edge {
int to; // 表示这条边的另外一个顶点
int next; // 指向下一条边的数组下标,值为-1表示没有下一条边
};


// head[i] 表示顶点`i`的第一条边的数组下标,-1表示顶点`i`没有边
int head[V];
Edge edge[E];


// 链式前向星初始化,只需要初始化顶点数组就可以了
memset(head, -1, sizeof(head));


// 增加边的方式
// 新增边 a -> b,该边的数组下标为`id`
inline void AddEdge(int a, int b, int id)
{

edge[id].to = b;
edge[id].next = head[a]; // 新增的边要成为顶点`a`的第一条边,而不是最后一条边
head[a] = id;
return;
}

// 遍历从`a`点出去的所有边
for (int i=head[a]; i!=-1; i=e[i].next) {
// e[i] 就是你当前遍历的边 a -> e[i].to
}

优点

缺点

总结

对于邻接矩阵存图来说,由于内存消耗的局限性,它的适用范围比较狭窄,几乎只能在简单图论题目中见到。

邻接表存图最为常见的一种,绝大部分采用C++STL中的vector实现,一般情况下大部分图论题目都能使用该存图方式。

但是链式前向星其实是一种较好替代邻接表来存图的数据结构,在邻接表存图不能使用时可以使用,几乎可以用于全部图论题目。

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值