图的存储
图的存储必须要完整、准确的反映顶点集和边集的信息。
无论是有向图还是无向图,主要的存储方式都有两种:邻接矩阵和邻接表。
前者属于图的顺序存储结构,后者属于图的链接存储结构。
邻接矩阵法
所谓邻接矩阵存储,是指用一个一维数组存储图中顶点的信息,用一个二维数组存储图中边的信息(即各个顶点之间的邻接关系),存储顶点之间邻接关系的二维数组称为邻接矩阵。
若图G是一个有n个顶点的图,那么图的邻接矩阵是一个n阶方阵A。
A
[
i
]
[
j
]
=
{
1
,
若
(
v
i
,
v
j
)
或
<
v
i
,
v
j
>
是
E
中
的
边
0
,
若
(
v
i
,
v
j
)
或
<
v
i
,
v
j
>
不
是
E
中
的
边
A[i][j]=\begin{cases} 1,\quad 若(v_i,v_j)或<v_i,v_j>是E中的边\\ 0, \quad若(v_i,v_j)或<v_i,v_j>不是E中的边\\ \end{cases}
A[i][j]={1,若(vi,vj)或<vi,vj>是E中的边0,若(vi,vj)或<vi,vj>不是E中的边
例如:
![](https://img-blog.csdnimg.cn/d334038a9875418d89ec671f66a789b8.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA6Jab5a6a6LCU55qE54yrb3Zv,size_8,color_FFFFFF,t_70,g_se,x_16)
![](https://i-blog.csdnimg.cn/blog_migrate/717b98c578bdd85b5942baec6300188f.png)
A 1 = [ 0 1 1 1 1 0 0 0 1 0 0 1 1 0 1 0 ] A 2 = [ 0 1 1 0 0 0 0 0 0 0 0 1 1 0 0 0 ] A_1=\begin{bmatrix} 0&1&1&1\\ 1&0&0&0\\ 1&0&0&1\\ 1&0&1&0\\ \end{bmatrix} \quad\quad\quad A_2=\begin{bmatrix} 0&1&1&0\\ 0&0&0&0\\ 0&0&0&1\\ 1&0&0&0\\ \end{bmatrix} A1=⎣⎢⎢⎡0111100010011010⎦⎥⎥⎤A2=⎣⎢⎢⎡0001100010000010⎦⎥⎥⎤
不难看出,无向图的邻接矩阵是对称的,而有向图的邻接矩阵不一定对阵。
这是因为在无向图中,
(
v
i
,
v
j
)
=
(
v
j
,
v
i
)
(v_i,v_j)=(v_j,v_i)
(vi,vj)=(vj,vi),是同一条边,故一定有
A
[
i
]
[
j
]
=
A
[
j
]
[
i
]
A[i][j] = A[j][i]
A[i][j]=A[j][i]。
对于带权图,若
v
i
v_i
vi和
v
j
v_j
vj之间有边相连,则邻接矩阵中对应项存放着该边对应的权值,若顶点
v
i
v_i
vi和
v
j
v_j
vj不相连,则用
∞
\infty
∞来代表这两个顶点之间不存在边:
A
[
i
]
[
j
]
=
{
w
i
j
,
若
(
v
i
,
v
j
)
或
<
v
i
,
v
j
>
是
E
中
的
边
0
,
若
(
v
i
,
v
j
)
或
<
v
i
,
v
j
>
不
是
E
中
的
边
且
i
=
j
∞
,
若
(
v
i
,
v
j
)
或
<
v
i
,
v
j
>
不
是
E
中
的
边
且
i
≠
j
A[i][j]=\begin{cases} w_{ij},\quad\ 若(v_i,v_j)或<v_i,v_j>是E中的边\\ 0, \quad\quad若(v_i,v_j)或<v_i,v_j>不是E中的边且i=j\\ \infty,\quad若(v_i,v_j)或<v_i,v_j>不是E中的边且i≠j\\ \end{cases}
A[i][j]=⎩⎪⎨⎪⎧wij, 若(vi,vj)或<vi,vj>是E中的边0,若(vi,vj)或<vi,vj>不是E中的边且i=j∞,若(vi,vj)或<vi,vj>不是E中的边且i=j
例如:
![](https://i-blog.csdnimg.cn/blog_migrate/1e0cf462e87239017bf5710001b8adcc.png)
A 3 = [ 0 12 8 ∞ ∞ 0 ∞ ∞ ∞ ∞ 0 9 4 ∞ ∞ 0 ] A_3=\begin{bmatrix} 0&12&8&\infty\\ \infty&0&\infty&\infty\\ \infty&\infty&0&9\\ 4&\infty&\infty&0\\ \end{bmatrix} A3=⎣⎢⎢⎡0∞∞4120∞∞8∞0∞∞∞90⎦⎥⎥⎤
图的邻接矩阵存储结构定义如下:
#define maxVertexNum 30 //图中顶点数目的最大值
typedef char VertexType; //顶点的数据类型
typedef int EdgeType; //带权图中边上权值的数据类型
typedef struct{
int vexnum,edgenum; //图中实际顶点个数和边的条数
VertexType Vextex[maxVertexNum]; //顶点表
EdgeType Edge[maxVertexNum][maxVertexNum]; //邻接矩阵
}MGraph;
邻接矩阵表示法的空间复杂度为 O ( n 2 ) O(n^2) O(n2),其中n为图的顶点数。
邻接矩阵表示法的特点
1、 无向图的邻接矩阵一定是一个对称矩阵,并且唯一。因此,在实际存储邻接矩阵时只需存储上(或下)三角矩阵的元素。
2、对于无向图,邻接矩阵的第 i 行(或第 i 列)非零元素的个数正好是第 i 个顶点的度。
3、 对于有向图,邻接矩阵的第 i 行(或第 i 列)非零元素的个数正好是第 i 个顶点的出度(或入度)。
4、邻接矩阵法存储图,很容易确定两个顶点之间是否有边相连,但要确定图中有多少条边,必须按行按列对每个元素进行检测,所花费的时间代价很大。这是使用邻接矩阵存储图的局限性。
5、稠密图适合用邻接矩阵的存储表示。
邻接表法
当一个图为稀疏图时,使用邻接矩阵表示法显然要浪费大量的存储空间,而图的邻接表法结合了顺序存储和链式存储方法,大大减少了这种不必要的浪费。
邻接表:是指对图G中每个顶点
v
i
{v_i}
vi建立一个单链表,第 i 个单链表中的结点表示依附于顶点
v
i
{v_i}
vi的边(对有向图来说则是以顶点
v
i
{v_i}
vi为尾的弧),这个单链表就称为顶点
v
i
{v_i}
vi的边表(对于有向图则称为出边表)。
边表的头指针和顶点的数据信息采用顺序存储(称为顶点表),所以在邻接表中存在两种结点:顶点表结点和边表结点。
顶 点 域 边 表 头 指 针 d a t a f i r s t a r c 邻 接 域 指 针 域 a d j v e x n e x t a r c \begin{array}{|c|c|} \hline {顶点域}&{边表头指针}\\ \hline {data}&{firstarc}\\ \hline \end{array} \quad\quad \begin{array}{|c|c|} \hline {邻接域}&{指针域}\\ \hline {adjvex}&{nextarc}\\ \hline \end{array} 顶点域data边表头指针firstarc邻接域adjvex指针域nextarc
有时候,对于网(带权图),其边表会增加权值项,即
邻
接
域
信
息
域
指
针
域
a
d
j
v
e
x
i
n
f
o
n
e
x
t
a
r
c
\begin{array}{|c|c|c|} \hline {邻接域}&{信息域}&{指针域}\\ \hline {adjvex}&{info}&{nextarc}\\ \hline \end{array}
邻接域adjvex信息域info指针域nextarc
例如
![](https://i-blog.csdnimg.cn/blog_migrate/b791a22cad95e9d57c251a603eed436e.png)
图的邻接表存储结构定义如下:
typedef struct ENode{ //边表结点
int adjvex; //弧指向的顶点的位置
struct ENode *nextarc; //指向下一条弧的指针
//InfoType info; //网的边权值
}EdgeNode;
typedef struct VNode{ //顶点表结点
VertexType data; //顶点的数据
struct ENode *firstarc; //边链表的头指针
}VertexNode;
typedef struct{
VertexNode VertexList[maxVertexNum]; //顶点表
int vexnum,edgenum; //图的顶点数和边数
}ALGraph; //以邻接表存储的图类型
邻接表存储的特点
1、若G为无向图,则所需的存储空间为O(|V| + 2|E|),因为每条边在邻接表中出现了两次;若G为有向图,则所需的存储空间为O(|V| + |E|)。
2、稀疏图通常适合用邻接表表示,可以极大节省存储空间。
3、在邻接表中,给定一个结点,能很容易找出它的所有邻边,只需要读取它的邻接表;但是,若要确定给定的两个顶点之间是否存在边,则在邻接表中需要在相应结点对应的边表中查找另一结点,效率较邻接矩阵低。
4、求一个给定顶点的出度只需要计算其邻接表中的结点个数;求其顶点的入度则需要遍历全部的邻接表。因此,也有人采用逆邻接表的存储方式来加速求解给定顶点的入度。
5、图的邻接表表示并不唯一,因为在每个顶点对应的单链表中,各边结点的链接次序可以是任意的,它取决于建立邻接表的算法及边的输入次序。
无向图的邻接多重表表示
邻接多重表是无向图的另一种链式存储结构,主要用在基于边的图算法中。
在邻接表中,容易求的顶点和边的各种信息,但是在邻接表中求两个顶点之间是否存在边时,需要分别在两个顶点的边表中遍历,效率很低。
在邻接多重表中,每条边用一个结点表示,即用边结点表示。
标
记
域
顶
点
域
指
针
域
顶
点
域
指
针
域
m
a
r
k
v
e
r
t
e
x
1
l
i
n
k
1
v
e
r
t
e
x
2
l
i
n
k
2
\begin{array}{|c|c|c|c|c|} \hline {标记域}&{顶点域}&{指针域}&{顶点域}&{指针域}\\ \hline {mark}&{vertex1}&{link1}&{vertex2}&{link2}\\ \hline \end{array}
标记域mark顶点域vertex1指针域link1顶点域vertex2指针域link2
边结点:
- mark:标志域,可用于标记该边是否被处理过
- vertex1、vertex2:顶点域,用于指明该边的两个顶点在图中的位置
- link1、link2:指针域,分别指向下一条依附于顶点vertex1和vertex2的边
顶 点 域 指 针 域 d a t a f i r s t e d g e \begin{array}{|c|c|} \hline {顶点域}&{指针域}\\ \hline {data}&{firstedge}\\ \hline \end{array} 顶点域data指针域firstedge
顶点结点:
- data:数据域,存储该顶点的信息
- firstedge:指针域,指向第一条依附于该顶点的边
例如:
邻接多重表的存储结构定义如下:
typedef struct ENode{ //边结点
int mark; //标志域
int vertex1, vertex2; //该边依附的两个顶点
struct ENode *link1, *link2; //分别指向两个顶点的下一条边
}EdgeNode;
typedef struct VNode{ //顶点结点
VertexType data; //顶点信息
struct ENode *firstedge; //指向第一条依附于该顶点的边
}VertexNode;
typedef struct{
VertexNode VertexLink[maxVertexNum]; //顶点表
int vexnum, edgenum; //图的顶点数和边数
}AMLGraph;//以邻接多重表存储的图类型
邻接多重表的特点
1、在邻接多重表中,所有依附于同一顶点的边都链接在同一个单链表中。只要从某个顶点 i 出发,就可以找出依附于该顶点的所有边,以及它的所有邻接顶点。
2、由于每条边依附于两个结点,所以每个边结点同时链接在两个链表中。
有向图的十字链表
十字链表是有向图的一种链式存储结构。在十字链表中,对应于有向图中的每条弧有一个结点,对应于每个顶点也有一个结点,结点结构如下所示:
标
记
域
尾
域
头
域
指
针
域
指
针
域
m
a
r
k
t
a
i
l
v
e
x
h
e
a
d
v
e
x
h
l
i
n
k
t
l
i
n
k
\begin{array}{|c|c|c|c|c|} \hline {标记域}&{尾域}&{头域}&{指针域}&{指针域}\\ \hline {mark}&{tailvex}&{headvex}&{hlink}&{tlink}\\ \hline \end{array}
标记域mark尾域tailvex头域headvex指针域hlink指针域tlink
边结点:
- mark:标记域,可用于标记是否被处理过
- tailvex、headvex:尾域和头域,分别指示弧尾和弧头这两个顶点在图中的位置
- hlink:指向弧头相同的下一条弧
- tlink:指向弧尾相同的下一条弧
顶 点 域 指 针 域 指 针 域 d a t a f i r s t i n f i r s t o u t \begin{array}{|c|c|c|} \hline {顶点域}&{指针域}&{指针域}\\ \hline {data}&{firstin}&{firstout}\\ \hline \end{array} 顶点域data指针域firstin指针域firstout
顶点结点:
- data:数据域,存储该顶点的信息
- firstin:指针域,指向以该顶点为弧头的第一个弧结点
- firstout:指针域,指向以该顶点为弧尾的第一个弧结点
例如:
图的十字链表存储结构定义如下:
typedef struct ENode{ //边结点
int mark; //标记
int tailvex, headvex; //弧的弧头和弧尾的位置
struct ENode *hlink, *tlink; //分别指向弧头相同和弧尾相同的结点
}EdgeNode;
typedef struct VNode{ //顶点结点
VertexType data; //顶点信息
struct ENode *firstin, *firstout; //分别指向第一条弧头或弧尾相同的下一条边
}VertexNode;
typedef struct{
VertexNode VertexLink[maxVertexNum]; //顶点表
int vexnum, edgenum; //图的顶点数和边数
}GLGraph; //以十字链表存储的图类型
十字链表存储的特点
在十字链表中,既容易找到以 v i {v_i} vi为尾的弧,又容易找到以 v i {v_i} vi为头的弧,因而容易求的顶点的出度和入度。