图的存储必须要完整、准确地反映顶点集合边集的信息,根据不同图的结构和算法,采用不同的存储方式将对程序的效率产生相当大的影响,因此所选的存储结构应适合于欲求解的问题。
目录
1、邻接矩阵法
所谓邻接矩阵存储,是指用一个一维数组存储图中顶点的信息,用一个二维数组存储图中边的信息(即各顶点之间的邻接关系),存储顶点之间邻接关系的二维数组称为邻接矩阵。
结点数为n的图G=(V,E)的邻接矩阵A是n*n的。将G的顶点编号为v1,v2,...,vn。若(vi,vj)∈E,则A[i][j] = 1,否则A[i][j] = 0。
对于带权图而言,若顶点vi和vj之间有边相连,则邻接矩阵中对应项应该存放着该边的权值,若顶点vi和vj不相连,则用来代表两个顶点之间不存在边:
有向图、无向图的网对应的邻接矩阵示例如下图所示:
图45-2 有向图、无向图及网的邻接矩阵
图的邻接矩阵存储结构定义如下:
#define MaxVertexNum 100 //顶点数目的最大值
typedef char VertexType; //顶点的数据类型
typedef int EdgeType; //带权图中边上权值的数据类型
typedef struct {
VertexType Vex[MaxVertexNum]; //顶点表
EdgeType Edge[MaxVertexNum][MaxVertexNum]; //邻接矩阵,边表
int vexnum, arcnum; //图的当前顶点数和弧数
}MGraph;
注意:
① 在简单应用中,可直接用二维数组作为图的邻接矩阵(顶点信息等均可忽略)。
② 当邻接矩阵中的元素仅表示相应的边是否存在时,EdgeType可定义为值为0和1的枚举类型。
③ 无向图的邻接矩阵是对称矩阵,对规模特大的邻接矩阵可采用压缩存储。
④ 邻接矩阵表示法的空间复杂度为O(),其中n为图的顶点数|V|。
图的邻接矩阵存储表示法具有以下特点:
① 无向图的邻接矩阵一定是一个对称矩阵(并且唯一)。因此,在实际存储邻接矩阵时只需存储上(或下)三角矩阵的元素。
② 对于无向图,邻接矩阵的第i行(或第i列)非零元素(或非∞元素)的个数正好是第i个顶点的度TD(vi)。
③ 对于有向图,邻接矩阵的第i行(或第i列)非零元素(或非∞元素)的个数正好是第i个顶点的出度OD(vi)[或入度ID(vi)]
④ 用邻接矩阵法存储图,很容易确定图中任意两个顶点之间是否有边相连。但是,要确定图中有多少条边,则必须按行、按列队每个元素进行检测,所花费的代价很大。
⑤ 稠密图适合使用邻接矩阵存储,因为空间利用率高
⑥ 设图G的邻接矩阵为A,的元素[i][j]等于由顶点i到顶点j的长度为n的路径的数目。
2、邻接表法
当一个图为稀疏图时,使用邻接矩阵法显然要浪费大量的存储空间,而图的邻接表法结合了顺序存储和链式存储方法,大大减少了这种不必要的浪费。
所谓邻接表,是指对图G中的每个顶点vi建立一个单链表,第i个单链表中的结点表示依附于顶点vi的边(对于有向图则是以顶点vi为尾的弧),这个单链表就称为顶点vi的边表(对于有向图则称为出边表)。边表的头指针和顶点的数据信息采用顺序存储(称为顶点表),所以在邻接表中存在两种结点:顶点表结点和边表结点,如下图所示。
图45-2 顶点表和边表结点结构
顶点表结点由顶点域(data)和指向第一条邻接边的指针(firstarc)构成,边表(邻接表)结点由邻接点域(adjvex)和指向下一条邻接边的指针域(nextarc)构成。
无向图和有向图的实例分别如图45-3和45-4所示。
图45-3 无向图邻接表示法
图45-4 有向图邻接表示法
图的邻接表存储结构定义如下:
typedef struct ArchNode //边表结点
{
int adjvex; //该弧所指向弧点的位置
struct ArchNode *next; //指向下一条弧的指针
int info; //网的边权重
}ArchNode;
typedef struct VNode
{
VertexType data;
ArchNode *first; //指向第一条依附于该顶点的弧的指针
}VNode,AdjList[MaxVertexNum];
typedef struct {
AdjList vertices; //邻接表
int vexnum, arcnum; //图的当前顶点数和弧数
}MGraph;
图的邻接表存储方法具有以下特点:
① 若G为无向图,则所需的存储空间为O(|V|+2|E|);若G为有向图,则所需的存储空间为O(|V|+|E|)。
② 对于稀疏图,采用邻接表表示将极大节省存储空间。
③ 在邻接表中,给定一顶点,能很容易的找出它所有的邻边,因为只需要读取它的邻接表。在邻接矩阵中,相同的操作则需要扫描一行,花费的时间为O(n)。但是,若要确定给定的两个顶点间是否存在边,则在邻接矩阵中可以立刻查到,而在邻接矩阵中则需要在相应结点对应的边表中查找另一结点,效率较低。
④ 在有向图的邻接表示中,求一则需要遍历全部的邻接表个给定结点的出度只需计算其邻接表中的结点个数;但求其顶点的入度则需要遍历全部的链接表,因此,也有人采用逆邻接表(任一表头结点下的边结点的数量是图中该结点入度的弧的数量,与邻接表相反。图的逆邻接表反映的是节点的入度邻接情况。)的存储方式来加速求解给定结点的入度。
⑤ 图的邻接表并不唯一,因为在每个顶点对应的单链表中,各边结点的链接次序可以是任意的,它取决于建立链接表的算法及边的输入次序。
3、十字链表
十字链表是有向图的一种存储结构。在十字链表中,对于有向图中的每条弧有一个结点,对应于每个顶点也有一个结点。这些结点的结构如下图所示。
弧结点中有5个域:尾域(tailvex)和头域(headvex)分别指示弧尾和弧头这两个顶点在图中的位置:链域hlink指向弧头相同的下一条弧;链域tlink指向弧尾相同的下一条弧;info域指向该弧的相关信息(比如权重)。这样,弧头相同的弧就在同一个链表上,弧尾相同的弧也在一个链表上。
顶点域中有三个域:data域存放顶点相关的数据信息,如顶点名称;firstin和firstout两个域分别指向以该顶点为弧头或弧尾的第一个弧结点。
图45-5为有向图的十字链表表示法。注意,顶点结点之间是顺序存储的。
图45-5 有向图的十字链表表示
我来大致解释一下这个图
图中4个顶点结点0,1,2,3对应图中的V1,V2,V3,V4。7个弧结点对应7条有向边。比如tailvex=0,headvex=1代表从V1-V2的有向边。
V1:V1的firstin指向以V3-V1有向边的弧结点,然后V3-V1有向边的弧结点里的hlink指向V3-V1结点。至此关于V1结点作为弧头的信息构造完毕。
V1的firstout指向V1-V2有向边的弧结点,然后V3-V1有向边的弧结点的hlink指向V1-V3有向边的弧结点。至此关于V1的信息构造完毕。
V2、V3、V4就不废话了。
在十字链表中,既容易找到Vi为尾的弧,又容易找到Vi为头的弧,因而容易求得顶点的出入度。
4、邻接多重表
邻接多重表是无向图的另一种链式存储结构。
在邻接表中,容易求得顶点和边的各种信息,但在邻接表中两个结点之间是否存在边而对边执行删除操作的时候,需要分别在两个顶点的边表中遍历,效率较低。
与十字链表类似,在邻接多重表中,每条边用一个结点表示,其结构如下所示。
mark:标志域,可用以标记该条边是否被搜索过
ivex/jvex:该边依附的两个顶点在图中的位置
ilink:指向下一条依附于顶点ivex的边
jlink:指向下一条依附于顶点jviex的边
info:指向和边有关的各种信息的指针域
每个结点也可用一个结点表示,它是由如下所示的两个域组成。
data:顶点的相关信息
firstedge:第一条依附于该顶点的边
在邻接多重表中,所有依附于同一个顶点的边串联在同一链表中,由于每条边依附于两个顶点,因此每个边结点同时连接在两个链表中。对无向图而言,其邻接多重表和邻接表的差别仅在于,同一条边在邻接表中用两个结点表示。而在邻接多重表中只有一个结点。
图45-6所示为无向图的邻接多重表表示法。邻接多重表的各种基本操作的实现和邻接表类似,
图46-6 无向图的邻接多重表表示