数据结构与算法——第7章-图-图的顺序存储结构(7.4)

一 概述

1
2
3
4
1.相关概念
2.图的顺序存储代码表示
3.图转换为数组
4.示例代码

二 相关概念

2.1 图的存储

1
2
3
4
5
6
7
使用图结构表示的数据元素之间虽然具有“多对多”的关系,但是同样可以采用顺序存储,也就是使用数组有效地存储图。

使用数组存储图时,需要使用两个数组,一个数组存放图中顶点本身的数据(一维数组),
另外一个数组用于存储各顶点之间的关系(二维数组)。

存储图中各顶点本身数据,使用一维数组就足够了;存储顶点之间的关系时,
要记录每个顶点和其它所有顶点之间的关系,所以需要使用二维数组。

2.2 图和网

1
2
3
4
5
6
7
不同类型的图,存储的方式略有不同,根据图有无权,可以将图划分为两大类:图和网 。

图,包括无向图和有向图;网,是指带权的图,包括无向网和有向网。
存储方式的不同,指的是:在使用二维数组存储图中顶点之间的关系时,
如果顶点之间存在边或弧,在相应位置用 1 表示,反之用 0 表示;
如果使用二维数组存储网中顶点之间的关系,顶点之间如果有边或者弧的存在,在数组的相应位置存储其权值;
反之用 0 表示。

三 图的顺序存储代码表示

结构代码表示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define MAX_VERtEX_NUM 20                   //顶点的最大个数
#define VRType int //表示顶点之间的关系的变量类型
#define InfoType char //存储弧或者边额外信息的指针变量类型
#define VertexType int //图中顶点的数据类型
typedef enum{DG,DN,UDG,UDN}GraphKind; //枚举图的 4 种类型
typedef struct {
VRType adj; //对于无权图,用 1 或 0 表示是否相邻;对于带权图,直接为权值。
InfoType * info; //弧或边额外含有的信息指针
}ArcCell,AdjMatrix[MAX_VERtEX_NUM][MAX_VERtEX_NUM];

typedef struct {
VertexType vexs[MAX_VERtEX_NUM]; //存储图中顶点数据
AdjMatrix arcs; //二维数组,记录顶点之间的关系
int vexnum,arcnum; //记录图的顶点数和弧(边)数
GraphKind kind; //记录图的种类
}MGraph;

四 图转换为数组

4.1 有向图和无向图

1
2
例如,存储图 1 中的无向图(B)时,除了存储图中各顶点本身具有的数据外,
还需要使用二维数组存储任意两个顶点之间的关系。

4.2 无向图对应的二维数组arcs

1
2
3
4
5
6
7
8
9
10
11
由于 (B) 为无向图,各顶点没有权值,所以如果两顶点之间有关联,相应位置记为 1 ;
反之记为 0 。构建的二维数组如图 2 所示。

在此二维数组中,每一行代表一个顶点,依次从 V1 到 V5 ,每一列也是如此。
比如 arcs[0][1] = 1 ,表示 V1 和 V2 之间有边存在;而 arcs[0][2] = 0,说明 V1 和 V3 之间没有边。

对于无向图来说,二维数组构建的二阶矩阵,实际上是对称矩阵,
在存储时就可以采用压缩存储的方式存储下三角或者上三角。

通过二阶矩阵,可以直观地判断出各个顶点的度,为该行(或该列)非 0 值的和。
例如,第一行有两个 1,说明 V1 有两个边,所以度为 2。

4.3 有向图对应的二维数组arcs

1
2
3
4
5
6
存储图 1 中的有向图(A)时,对应的二维数组如图 3 所示:

例如,arcs[0][1] = 1 ,证明从 V1 到 V2 有弧存在。
且通过二阶矩阵,可以很轻松得知各顶点的出度和入度,出度为该行非 0 值的和,入度为该列非 0 值的和。
例如,V1 的出度为第一行两个 1 的和,为 2 ; V1 的入度为第一列中 1 的和,为 1 。
所以 V1 的出度为 2 ,入度为 1 ,度为两者的和 3 。

五 示例代码

5.1 图的顺序存储结构C语言实现

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
#include <stdio.h>
#define MAX_VERtEX_NUM 20 //顶点的最大个数
#define VRType int //表示顶点之间的关系的变量类型
#define InfoType char //存储弧或者边额外信息的指针变量类型
#define VertexType int //图中顶点的数据类型
typedef enum{DG,DN,UDG,UDN}GraphKind; //枚举图的 4 种类型
typedef struct {
VRType adj; //对于无权图,用 1 或 0 表示是否相邻;对于带权图,直接为权值。
InfoType * info; //弧或边额外含有的信息指针
}ArcCell,AdjMatrix[MAX_VERtEX_NUM][MAX_VERtEX_NUM];

typedef struct {
VertexType vexs[MAX_VERtEX_NUM]; //存储图中顶点数据
AdjMatrix arcs; //二维数组,记录顶点之间的关系
int vexnum,arcnum; //记录图的顶点数和弧(边)数
GraphKind kind; //记录图的种类
}MGraph;
//根据顶点本身数据,判断出顶点在二维数组中的位置
int LocateVex(MGraph * G,VertexType v){
int i=0;
//遍历一维数组,找到变量v
for (; i<G->vexnum; i++) {
if (G->vexs[i]==v) {
break;
}
}
//如果找不到,输出提示语句,返回-1
if (i>G->vexnum) {
printf("no such vertex.\n");
return -1;
}
return i;
}
//构造有向图
void CreateDG(MGraph *G){
//输入图含有的顶点数和弧的个数
scanf("%d,%d",&(G->vexnum),&(G->arcnum));
//依次输入顶点本身的数据
for (int i=0; i<G->vexnum; i++) {
scanf("%d",&(G->vexs[i]));
}
//初始化二维矩阵,全部归0,指针指向NULL
for (int i=0; i<G->vexnum; i++) {
for (int j=0; j<G->vexnum; j++) {
G->arcs[i][j].adj=0;
G->arcs[i][j].info=NULL;
}
}
//在二维数组中添加弧的数据
for (int i=0; i<G->arcnum; i++) {
int v1,v2;
//输入弧头和弧尾
scanf("%d,%d",&v1,&v2);
//确定顶点位置
int n=LocateVex(G, v1);
int m=LocateVex(G, v2);
//排除错误数据
if (m==-1 ||n==-1) {
printf("no this vertex\n");
return;
}
//将正确的弧的数据加入二维数组
G->arcs[n][m].adj=1;
}
}
//构造无向图
void CreateDN(MGraph *G){
scanf("%d,%d",&(G->vexnum),&(G->arcnum));
for (int i=0; i<G->vexnum; i++) {
scanf("%d",&(G->vexs[i]));
}
for (int i=0; i<G->vexnum; i++) {
for (int j=0; j<G->vexnum; j++) {
G->arcs[i][j].adj=0;
G->arcs[i][j].info=NULL;
}
}
for (int i=0; i<G->arcnum; i++) {
int v1,v2;
scanf("%d,%d",&v1,&v2);
int n=LocateVex(G, v1);
int m=LocateVex(G, v2);
if (m==-1 ||n==-1) {
printf("no this vertex\n");
return;
}
G->arcs[n][m].adj=1;
G->arcs[m][n].adj=1;//无向图的二阶矩阵沿主对角线对称
}
}
//构造有向网,和有向图不同的是二阶矩阵中存储的是权值。
void CreateUDG(MGraph *G){
scanf("%d,%d",&(G->vexnum),&(G->arcnum));
for (int i=0; i<G->vexnum; i++) {
scanf("%d",&(G->vexs[i]));
}
for (int i=0; i<G->vexnum; i++) {
for (int j=0; j<G->vexnum; j++) {
G->arcs[i][j].adj=0;
G->arcs[i][j].info=NULL;
}
}
for (int i=0; i<G->arcnum; i++) {
int v1,v2,w;
scanf("%d,%d,%d",&v1,&v2,&w);
int n=LocateVex(G, v1);
int m=LocateVex(G, v2);
if (m==-1 ||n==-1) {
printf("no this vertex\n");
return;
}
G->arcs[n][m].adj=w;
}
}
//构造无向网。和无向图唯一的区别就是二阶矩阵中存储的是权值
void CreateUDN(MGraph* G){
scanf("%d,%d",&(G->vexnum),&(G->arcnum));
for (int i=0; i<G->vexnum; i++) {
scanf("%d",&(G->vexs[i]));
}
for (int i=0; i<G->vexnum; i++) {
for (int j=0; j<G->vexnum; j++) {
G->arcs[i][j].adj=0;
G->arcs[i][j].info=NULL;
}
}
for (int i=0; i<G->arcnum; i++) {
int v1,v2,w;
scanf("%d,%d,%d",&v1,&v2,&w);
int m=LocateVex(G, v1);
int n=LocateVex(G, v2);
if (m==-1 ||n==-1) {
printf("no this vertex\n");
return;
}
G->arcs[n][m].adj=w;
G->arcs[m][n].adj=w;//矩阵对称
}
}
void CreateGraph(MGraph *G){
//选择图的类型
scanf("%d",&(G->kind));
//根据所选类型,调用不同的函数实现构造图的功能
switch (G->kind) {
case DG:
return CreateDG(G);
break;
case DN:
return CreateDN(G);
break;
case UDG:
return CreateUDG(G);
break;
case UDN:
return CreateUDN(G);
break;
default:
break;
}
}
//输出函数
void PrintGrapth(MGraph G)
{
for (int i = 0; i < G.vexnum; i++)
{
for (int j = 0; j < G.vexnum; j++)
{
printf("%d ", G.arcs[i][j].adj);
}
printf("\n");
}
}
int main() {
MGraph G;//建立一个图的变量
CreateGraph(&G);//调用创建函数,传入地址参数
PrintGrapth(G);//输出图的二阶矩阵
return 0;
}

5.2 说明

1
2
3
4
5
注意:在此程序中,构建无向网和有向网时,对于之间没有边或弧的顶点,相应的二阶矩阵中存放的是 0。
目的只是为了方便查看运行结果,而实际上如果顶点之间没有关联,它们之间的距离应该是无穷大(∞)。


例如,使用上述程序存储图 4(a)的有向网时,存储的两个数组如图 4(b)所示:

5.3 结果

相应地运行结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
6,10
1
2
3
4
5
6
1,2,5
2,3,4
3,1,8
1,4,7
4,3,5
3,6,9
6,1,3
4,6,6
6,5,1
5,4,5
0 5 0 7 0 0
0 0 4 0 0 0
8 0 0 0 0 9
0 0 5 0 0 6
0 0 0 5 0 0
3 0 0 0 1 0

5.4 总结

1
2
总结一下,本节主要详细介绍了使用数组存储图的方法,在实际操作中使用更多的是链式存储结构,
例如邻接表、十字链表和邻接多重表,这三种存储图的方式放在下一节重点去讲。

六 参考

  • C语言中文网—图的顺序存储结构