如果要用图来解决问题,首先我们必须采用某种数据结构来存储和表示“图”。相对于数组、链表等来说,图的存储结构就复杂的多了。
- 首先,图上的任何一个顶点都可以被看作是第一个顶点,任意顶点的邻接顶点之间也不存在次序关系。还记得在《图论(一)基本概念》中的“同构图”吧,图的形状可以千变万化的。因此也就无法以数据元素在内存中的物理位置来表示元素之间的关系,也就是说,图不可能用数组这样简单的顺序存储结构来表示。
- 其次,如果使用链表一样的链式存储结构,不同顶点的邻接顶点数量是不一样的,相差可能很大,如何在操作和效率之间寻求平衡是个大难题。
因此,对于图来说,如何对它实现物理存储是是个难题,不过我们的前辈们已经解决了,现在来看两种比较常用的存储结构。
邻接矩阵
图的邻接矩阵(
A
d
j
a
c
e
n
c
y
Adjacency
Adjacency
M
a
t
r
i
x
Matrix
Matrix)存储方式是用两个数组来表示图。一个一维数组存储图中顶点信息,一个二维数组(称为邻接矩阵)存储图中的边或弧的信息。
设图
G
G
G有n个顶点,则邻接矩阵是一个
n
∗
n
n*n
n∗n的方阵,定义为:
a
r
c
[
i
]
[
j
]
=
{
1
,
若
(
v
i
,
v
j
)
∈
E
或
<
v
i
,
v
j
>
∈
E
0
,
反
之
arc[i][j]=\begin{cases} 1, &若(v_i,v_j)\in E或<v_i, v_j>\in E\\ 0, & 反之 \end{cases}
arc[i][j]={1,0,若(vi,vj)∈E或<vi,vj>∈E反之
若为有向图且带权值,则邻接矩阵表示为:
a r c [ i ] [ j ] = { W i j , 若 ( v i , v j ) ∈ E 或 < v i , v j > ∈ E , 且 i ≠ j ∞ , 反 之 arc[i][j]=\begin{cases} W_{ij}, &若(v_i,v_j)\in E或<v_i, v_j>\in E,且i\neq j\\ \infty, & 反之 \end{cases} arc[i][j]={Wij,∞,若(vi,vj)∈E或<vi,vj>∈E,且i̸=j反之
邻接矩阵的存储结构
有了上述的理解,那我们可以写出如下的代码来表示邻接矩阵的存储结构:
typedef char VextexType; //顶点类型(自定义)
typedef int EdgeType; //边上权值类型(自定义)
#define MAXVEX 100 //最大顶点数
#define INFINITY 65535 //代替 无穷
typedef struct {
VextexType vexs[MAXVEX]; //顶点表
EdgeType arc[MAXVEX][MAXVEX]; //邻接矩阵
int numVextexes, numEdges; //图中当前顶点数和边数
}MGraph;
通过该结构定义,我们构造一个图:
void CreateMGraph(MGraph *G) {
int i,j,k,w;
//读入顶点个数和边数
cin >> G->numVertexes >> G->numEdges;
//建立顶点信息表
for (i = 0; i < G->numVertexes; ++i) {
cin >> G->vexs[i];
}
//初始化邻接矩阵
for (i = 0; i < G->numVertexes; ++i) {
for (j = 0; j < numVertexes; ++j) {
G->arc[i][j] = INFINITY;
}
}
//读入边权值,构建邻接矩阵
for (k = 0; k < numEdges; ++k) {
cin >> i >> j >> w;
G->arc[i][j] = w;
G->arc[j][i] = G->arc[i][j]; //无向图是对称矩阵
}
}
从代码来看, n n n个顶点和 e e e条边的无向图的创建,时间复杂度为 O ( n + n 2 + e ) O(n + n^2 + e) O(n+n2+e),其中初始化邻接矩阵耗费 O ( n 2 ) O(n^2) O(n2)
邻接表
图的邻接表( A d − j a c e n c y L i s t Ad-jacency List Ad−jacencyList)的存储方式是图中的顶点用一维数组存储;图中每个顶点 v i v_i vi的所有邻接点构成一个线性表,由于邻接点的个数不定,所以用单链表存储。
如上图所示,图( b b b)表示了图( a a a)的邻接信息,可以看出,顶点表的各个节点由 d a t a data data和 f i r s t e d g e firstedge firstedge两个域表示, d a t a data data是数据域,存储顶点的信息, f i r s t e d g e firstedge firstedge是指针域,指向边表的第一个节点,即此顶点的第一个邻接点。边表节点由 a d j v e x adjvex adjvex和 w e i g h t weight weight、 n e x t next next三个域组成, a d j v e x adjvex adjvex是邻接点域,存储某顶点的邻接点在顶点表中的下标, w e i g h t weight weight存储权重, n e x t next next则存储指向边表中下一个节点的指针
邻接表的存储结构
根据上述理解,得出如下邻接表存储结构代码:
typedef char VertexType; //顶点类型
typedef int EdgeType; //边权值类型
#define MAXVEX 100 //最大顶点数
//边表节点
typedef struct EdgeNode {
//邻接点域,存储该顶点对应的下标
int adjvex;
//用于存储权值
EdgeType weight;
//指向下一个邻接点
struct EdgeNode *next;
}EdgeNode;
//顶点表节点
typedef struct VertexNode {
//顶点域,存储顶点信息
VertexType data;
//边表头指针
EdgeNode *firstedge;
}VertexNode, AdjList[MAXVEX];
typedef struct {
AdjList adjList;
//图中当前顶点数和边数
int numVextexes, numEdges;
}GraphAdjList;
通过该结构定义,我们构造一个图(无向图):
void CreateALGraph(GraphAdjList *G) {
int i,j,k;
//读入顶点个数和边数
cin >> G->numVextexes >> G->numEdges;
//读入顶点信息,建立顶点信息表
for (i = 0; i < numVextexes; ++i) {
cin >> G->adjList[i].data;
G->adjList[i].firstedge = NULL; //将边表置为空
}
//建立边表
for (k = 0; k < numEdges; ++k) {
cin >> i >> j; //读入顶点(vi, vj)下标
//将vj插入到vi的边表中
EdgeNode *e = new EdgeNode();
e->adjvex = j;
e->next = G->adjList[i].firstedge;
G->adjList[i].firstedge = e;
//将vi插入到vj的边表中
EdgeNode *e = new EdgeNode();
e->adjvex = i;
e->next = G->adjList[j].firstedge;
G->adjList[j].firstedge = e;
}
}
由代码分析可得,时间复杂度为 O ( n + e ) O(n + e) O(n+e)