7.2图的存储结构
本讲介绍 4 种较常用的存储表示法:①邻接矩阵表示法;②邻接表;③邻接 多重表;④十字链表。由于每种方法各有利弊,因此可以根据实际应用问题来选择合适的存储表示方法。
①邻接矩阵表示法
图的邻接矩阵表示法(Adjacency Matrix)也称作数组表示法。它采用两个数 组来表示图:一个是用于存储顶点信息的一维数组,另一个是用于存储图中顶点 之间关联关系的二维数组,这个关联关系数组被称为邻接矩阵。
若 G 是一具有 n 个顶点的无权图,G 的邻接矩阵是具有如下性质的 n×n 矩 阵 A:
若图 G 是一个有 n 个顶点的网,则它的邻接矩阵是具有如下性质的 n×n 矩 阵 A
例如:下图就是一个有向网及其邻接矩阵的示例。
邻接矩阵表示法的 C 语言描述如下:
#define MAX_VERTEX_NUM 20 /*最多顶点个数*/
#define INFINITY 32768 /*表示极大值,即∞*/
/* 图的种类:DG 表示有向图, DN 表示有向网, UDG 表示无向图, UDN 表示无向网 */
typedef enum
{DG, DN, UDG, UDN} GraphKind;
typedef char VertexData; /*假设顶点数据为字符型*/
typedef struct ArcNode{
AdjType adj; /* 对于无权图,用 1 或 0 表示是否相邻; 对带权图,则为权值类型 */
OtherInfo info;
}ArcNode;
typedef struct{
VertexData vertex[MAX_VERTEX_NUM]; /*顶点向 量*/
ArcNode arcs [MAX_VERTEX_NUM][MAX_VERTEX_NUM]; /*邻接矩阵*/
int vexnum, arcnum; /*图的顶点数和弧数*/
GraphKind kind; /*图的种类标志*/
}AdjMatrix; /*(Adjacency Matrix Graph)*/
邻接矩阵法的特点如下:
- 存储空间: 对于无向图而言,它的邻接矩阵是对称矩阵(因为若(vi,vj) ∈E(G),则(vj,vi)∈E(G)),因此可以采用特殊矩阵的压缩存储法, 即只存储其下三角即可,这样,一个具有 n 个顶点的无向图 G,它的邻 接矩阵需要 n(n-1)/2 个存储空间即可。但对于有向图而言 ,其中的 弧是有方向的,即若<vi,vj>∈E(G),不一定有<vj,vi>∈E(G),因此, 有向图的邻接矩阵不一定是对称矩阵,对于有向图的邻接矩阵的存储则 需要 n2个存储空间。
- 便于运算: 采用邻接矩阵表示法,便于判定图中任意两个顶点之间是否 有边相连,即根据 A[i,j]=0 或 1 来判断。另外还便于求得各个顶点的度。 对于无向图而言,其邻接矩阵第 i 行元素之和就是图中第 i 个顶点的度:
采用邻接矩阵存储法表示图,很便于实现图的一些基本操作,如实现访问图 G 中 V 顶点第一个邻接点的函数 FirstAdjVertex(G,v)可按如下步骤实现:
FirstAdjVertex(G,v): - ⑴ 首先,由 LocateVertex(G,v)找到 v 在图中的位置,即 v 在一维数组 vertex 中的序号 i。
- ⑵ 二维数组 arcs 中第 i 行上第一个 adj 域非零的分量所在的列号 j,便是 v 的第一个邻接点在图 G 中的位置。
- ⑶ 取出一维数组 vertex[j]中的数据信息,即与顶点 v 邻接的第一个邻接点 的信息。 对于稀疏图而言,不适于用邻接矩阵来存储,因为这样会造成存储空间的浪费。
用邻接矩阵法创建有向网的算法如下:
【算法描述】
int LocateVertex(AdjMatrix * G, VertexData v) /*求顶点位置函数*/
{
int j=Error,k;
for(k=0;k<G->vexnum;k++)
if(G->vertex[k]==v)
{
j=k;
break;
}
return(j);
}
int CreateDN(AdjMatrix *G) /*创建一个有向网*/
{
int i,j,k,weight;
VertexData v1,v2;
scanf("%d,%d",&G->arcnum,&G->vexnum); /*输入图的顶点数和弧数*/
for(i=0;i<G->vexnum;i++) /*初始化邻接矩阵*/
for(j=0;j<G->vexnum;j++)
G->arcs[i][j].adj=INFINITY;
for(i=0;i<G->vexnum;i++)
scanf("%c",&G->vertex[i]); /* 输入图的顶点*/
for(k=0;k<G->arcnum;k++)
{
scanf("%c,%c,%d",&v1,&v2,&weight);/*输入一条弧的两个顶点及权 值*/
i=LocateVex_M(G,v1);
j=LocateVex_M(G,v2);
G->arcs[i][j].adj=weight; /*建立弧*/
}
return(Ok);
}
该算法的时间复杂度为 O(n²+e×n),其中 O(n²)时间耗费在对二维数组 arcs 的每个分量的 arj 域初始化赋值上。O(e×n)的时间耗费在有向网中边权的赋值上。
②邻接表表示法
图的邻接矩阵表示法(即图的数组表示法)虽然有其自身的优点,但对于稀 疏图来讲,用邻接矩阵的表示方法会造成存储空间的很大浪费。邻接表 (Adjacency List)表示法实际上是图的一种链式存储结构。它克服了邻接矩阵的 弊病,基本思想是只存有关联的信息,对于图中存在的边信息则存储,而不相邻 接的顶点则不保留信息。在邻接表中,对图中的每个顶点建立一个带头结点的边 链表,如第 i 个单链表中的结点则表示依附于顶点 vi 的边(若是有向图,则表示 以 vi 为弧尾的弧)。每个边链表的头结点又构成一个表头结点表。
这样,一个 n 个顶点的图的邻接表表示由表头结点表与边表两部分构成:
(1) 表头结点表
由所有表头结点以顺序结构(向量)的形式存储,以 便可以随机访问任一顶点的边链表。表头结点的结构如下图(a)所示。 表头结点由两部分构成,其中**数据域(vexdata)**用于存储顶点的名或其 它有关信息;**链域(firstarc)**用于指向链表中第一个顶点(即与顶点 vi 邻接的第一个邻接点)。
(2) 边表:
由表示图中顶点间邻接关系的 n 个边链表组成。边链表中结点的结构如下图(b)所示,它由三部分组成,其中邻接点域(adjvex) 用于存放与顶点 vi相邻接的顶点在图中的位置;**链域(nextarc)**用于指 向与顶点 vi 相关联的下一条边或弧的结点;**数据域(info)**用于存放与 边或弧相关的信息(如赋权图中每条边或弧的权值等)。
如下图所示分别是图 G1、G2 邻接表表示示例,其中边表中顶点无顺序要求。
邻接表存储结构的形式化说明如下:
#define MAX_VERTEX_NUM 20 /*最多顶点个数*/
typedef enum{DG, DN, UDG, UDN} GraphKind; /*图的种类*/
typedef struct ArcNode
{
int adjvex; /*该弧指向顶点的位置*/
struct ArcNode *nextarc; /*指向下一条弧的指针*/
OtherInfo info; /*与该弧相关的信息*/
}ArcNode;
typedef struct VertexNode
{
VertexData data; /*顶点数据*/
ArcNode *firstarc; /*指向该顶点第一条弧的指针*/
}VertexNode;
typedef struct
{
VertexNode vertex[MAX_VERTEX_NUM];
int vexnum,arcnum; /*图的顶点数和弧数*/
GraphKind kind; /*图的种类标志*/
}AdjList; /*基于邻接表的图(Adjacency List Graph)*/
■存储空间 对于有 n 个顶点,e 条边的无向图而言,若采取邻接表作为存储结构,则需 要 n 个表头结点和 2e 个表结点。很显然在边很稀疏(即 e 远小于 n(n-1)/2 时) 的情况下,用邻接表存储所需的空间要比邻接矩阵所需的 n(n-1)/2 要节省得多。
■无向图的度 在无向图的邻接表中,顶点 vi的度恰好就是第 i 个单链表上结点的个数。
■有向图的度 在有向图中,第 i 个单链表上结点的个数只是顶点 vi 的出度,只需通过表头向量表中找到第 i 个顶点的边链表的头指针,实现顺链查找即可。
如果要判定任意两个顶点(vi 和 vj)之间是否有边或弧相连,则需要搜索所有的边链表,这样比较麻烦。
要求第 i 个顶点的入度,必须遍历整个邻接表。在所有边链表中查找邻接点 域的值为 i 的结点并计数求和。由此可见,对于用邻接表方式存储的有向图,求顶点的入度并不方便,它需要扫描整个邻接表才能得到结果。
一种解决的方法是逆邻接表法,我们可以对每一顶点 vi再建立一个逆邻接表, 即对每个顶点 vi建立一个所有以顶点 vi为弧头的弧的表,有向图 G1 的逆邻接表如下图所示。这样求顶点 vi的入度即是计算逆邻接表中第 i 个顶点的边链表中结点个数。
实质上这需要将图中的一条弧分别在邻接表与逆邻接表各自的边链表中存 储,下面介绍十字链表法,此方法优点是一条弧只被存放一次。
③ 十字链表
十字链表(Orthogonal List)是有向图的另一种链式存储结构。可以把它看成是 将有向图的邻接表和逆邻接表结合起来形成的一种链表。有向图中的每一条弧对 应十字链表中的一个弧结点,同时有向图中的每个顶点在十字链表中对应有一个结点,叫做顶点结点。这两类结点结构如下图所示。
例如:有向图 G1 的十字链表表示如下图所示。若有向图是稀疏图,则它的 邻接矩阵一定是稀疏矩阵,这时该图的十字链表表示法可以看成是其邻接矩阵的 链表表示法。只是在图的十字链表表示法中,弧结点所在的链表不是循环链表且 结点之间相对位置自然形成,不一定按顶点序号有序。另外,表头结点即顶点结点,它们之间并非循环链式连接,而是顺序存储。
图的十字链表结构形式化定义如下:
#define MAX_VERTEX_NUM 20 /*最多顶点个数*/
typedef enum{DG, DN, UDG, UDN} GraphKind; /*图的种类*/
typedef struct ArcNode
{
int tailvex, headvex;
struct ArcNode *hlink, *tlink;
}ArcNode;
typedef struct VertexNode
{
VertexData data; /*顶点信息*/
ArcNode *firstin, *firstout;
}VertexNode;
typedef struct
{
VertexNode vertex[MAX_VERTEX_NUM];
int vexnum, arcnum; /*图的顶点数和弧数*/
GraphKind kind; /*图的种类*/
}OrthList; /*图的十字链表表示法(Orthogonal List)*/
下面给出建立一个有向图的十字链表的算法:
【算法描述】
void CrtOrthList(OrthList *g) /*从终端输入 n 个顶点的信息和 e 条弧的信息,以建立一个有向图的十字链表*/
{
scanf(“%d,%d”,&n,&e); /*从键盘输入图的顶点个数和弧的个数*/
g->vexnum = n;
g->arcnum = e;
for (i=0;i<n;i++)
{
scanf(“%c”,&(g->vertex[i].data));
g->vertex[i] .firstin=NULL;
g->vertex[i] .firsout=NULL;
}
for (k=0;k<e;k++)
{
scanf(“%c,%c”,&vt,&vh);
i=LocateVertex(g,vt);
j = LocateVertex(g,vh);
p=( ArcNode*)malloc(sizeof(ArcNode));
p->tailvex=I;
p->headvex=j;
p->tlink = g->vertex[i].firstout;
g->vertex[i].firstout =p;
p->hlink = g->vertex[j].firstin;
g->vertex[j].firstin =p;
}
} /* CrtOrthList */
在十字链表中既能够很容易地找到以 vi 为尾的弧,也能够容易地找到以 vi 为头的弧,因此对于有向图,若采用十字链表作为存储结构,则很容易求出顶点 vi的度。此外,为有向图建立一个邻接表的算法和建立一个十字链表的算法的时 间复杂度是相同的。所以,在某些有向图的应用中,十字链表表示法更为合适。
④邻接多重表
邻接多重表(Adjacency Multi_list)是无向图的另外一种存储结构,之所以提出 邻接多重表这种存储结构是因为它能够提供更为方便的边处理信息。在无向图的邻接表表示法中,每一条边(vi,vj)在邻接表中都对应着两个结点,它们分别在第 i 个边链表 i 和第 j 个边链表中。这给图的某些边操作带来不便,如检测某条边是否被访问过,则需要同时找到表示该条边的两个结点,而这两个结点又分别在两个边链表中。邻接多重表是指将图中关于一条边的信息用一个结点来表示, 其结点结构如下图(a)所示,图中的每一个顶点也对应一个顶点结点,其结构如下图(b)所示。
邻接多重表的结构类型说明如下:
#define MAX_VERTEX_NUM 20 /*最多顶点个数*/
typedef struct EdgeNode
{
int mark,ivex,jvex;
struct EdgeNode *ilink, *jlink;
}EdgeNode;
typedef struct
{
VertexData data;
EdgeNode *firstedge;
}VertexNode;
typedef struct{
VertexNode vertex[MAX_VERTEX_NUM];
int vexnum, arcnum; /*图的顶点数和弧数*/
GraphKind kind; /*图的种类*/
}AdjMultiList; /*基于图的邻接多重表表示法(Adjacency Multi_list)*/