图的存储结构
1. 图
顶点:图中的数据元素。V是顶点的有穷非空集合。
弧:<v,w>表示从v到w的一条弧,v为弧尾,w为弧头。VR是两个顶点关系的集合。
无向图:若<v,w>∈VR,必有<w,v>∈VR,则此图为无向图。
有向图:无上述关系的图。
边:(v,w)表示无向图中顶点v和顶点w之间的一条边。
完全无向图和完全有向图
n表示顶点数,e表示弧或边的数,则对于无向图,e的取值范围为0~n(n-1)/2。对于有向图,e的取值范围为0~n(n-1)。完全无向图:有n(n-1)/2条边的无向图。完全有向图:有n(n-1)条弧的有向图。
稀疏图:弧或边较少的图。(e<nlogn)
稠密图:弧或边较多的图。
权(Weight):与图的边和弧相关的数。权可以表示从一个顶点到另一个顶点的距离或耗费。
子图:若图G=(V,{E})和G’=(V’,{E’})存在关系:V’ÍV且E’ÍE,则称G’为G的子图。
邻接,度,入度,出度:
对于无向图G=(V,{E}),若边(v,v’)∈E,则v和v’互为邻接点,该边依附于v和v’,或称边与两点相关联。顶点v的度TD(v)为和v相关联的边的数目。
对于有向图G=(V,{A}),若弧<v,v’>∈A,则v邻接到v’,v’邻接自v,弧与两点相关联。以v为头的弧的数目成为v的入度ID(v),以v为尾的弧的数目成为v的出度OD(v),顶点v的度为入度和出度之和。
弧或边的数目e和度TD的关系:e=1/2 (TD(v1)+TD(v2)+…+TD(vn))
路径(Path)、路径长度、回路(Cycle):
对无向图G=(V,E),若从顶点vi经过若干条边能到达vj,称顶点vi和vj是连通的,又称顶点vi到vj有路径。
对有向图G=(V,A),从顶点vi到vj有有向路径,指的是从顶点vi经过若干条有向边(弧)能到达vj。
路径上边或弧的数目称为该路径的长度。
在一条路径中,若没有重复相同的顶点,该路径称为简单路径;第一个顶点和最后一个顶点相同的路径称为回路(环);在一个回路中,若除第一个与最后一个顶点外,其余顶点不重复出现的回路称为简单回路(简单环)。
连通图、图的连通分量:
对无向图G=(V,E),若"vi ,vj ÎV,vi和vj都是连通的,则称图G是连通图,否则称为非连通图。若G是非连通图,则极大的连通子图称为G的连通分量。
对有向图G=(V,A),若"vi ,vj ÎV,都有以vi为起点, vj 为终点以及以vj为起点,vi为终点的有向路径,称图G是强连通图,否则称为非强连通图。若G是非强连通图,则极大的强连通子图称为G的强连通分量。
“极大”的含义:指的是对子图再增加图G中的其它顶点,子图就不再连通。
生成树、生成森林:
一个连通图(无向图)的生成树是一个极小连通子图,它含有图中全部n个顶点和只有足以构成一棵树的n-1条边,称为图的生成树。
关于无向图的生成树的几个结论:
a) 一棵有n个顶点的生成树有且仅有n-1条边;
b) 如果一个图有n个顶点和小于n-1条边,则是非连通图;
c) 如果多于n-1条边,则一定有环;
d) 有n-1条边的图不一定是生成树。
有向图的生成森林是这样一个子图,由若干棵有向树组成,含有图中全部顶点。有向树是只有一个顶点的入度为0 ,其余顶点的入度均为1的有向图。
网:每个边(或弧)都附加一个权值的图,称为带权图。带权的连通图(包括弱连通的有向图)称为网或网络。
2. 图的存储结构
2.1 数组表示法(邻接矩阵表示法)
基本思想:对于有n个顶点的图,用一维数组vexs[n]存储顶点信息,用二维数组A[n][n]存储顶点之间关系的信息。该二维数组称为邻接矩阵。在邻接矩阵中,以顶点在vexs数组中的下标代表顶点,邻接矩阵中的元素A[i][j]存放的是顶点i到顶点j之间关系的信息。
数组表示法容易确定两个顶点之间是否有弧或边,并容易求得各个顶点的度。
//图的邻接矩阵存储表示 #define INFINITY INT_MAX //最大值∞ #define MAX_VERTEX_NUM 20 //顶点数 typedef enum{DG,DN,UDG,UDN} GraphKind; //{有向图,有向网,无向图,无向网} typedef struct ArcCell { 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;
2.1.1 无向图的数组表示
(1) 无权图的邻接矩阵
无向无权图G=(V,E)有n(n≧1)个顶点,其邻接矩阵是n阶对称方阵。其元素的定义如下:
(2) 带权图的邻接矩阵
无向带权图G=(V,E)的邻接矩阵元素的定义如下:
(3) 无向图邻接矩阵的特性
a) 邻接矩阵是对称方阵;
b) 对于顶点vi,其度数是第i行(或第i列)的非0元素的个数;
c) 无向图的边数是上(或下)三角形矩阵中非0元素个数。
2.1.2 有向图的数组表示
(1) 无权图的邻接矩阵
若有向无权图G=(V,E)有n(n≧1)个顶点,其邻接矩阵元素定义如下:
(2) 带权图的邻接矩阵
有向带权图G=(V,E)的邻接矩阵元素的定义如下:
(3) 有向图邻接矩阵的特性
a) 对于顶点vi,第i行的非0元素的个数是其出度OD(vi);第i列的非0元素的个数是其入度ID(vi)。
b) 邻接矩阵中非0元素的个数就是图的弧的数目。
2.2 邻接表
基本思想:对图的每个顶点建立一个单链表,存储该顶点所有邻接顶点及其相关信息。每一个单链表设一个表头结点。第i个单链表表示依附于顶点Vi的边(对有向图是以顶点Vi为头或尾的弧)。
2.2.1 结点结构
链表中的结点称为表结点,每个结点由三个域组成,如图所示。其中邻接点域(adjvex)指示与顶点Vi邻接的顶点在图中的位置(顶点编号),链域(nextarc)指向下一个与顶点Vi邻接的表结点,数据域(info)存储和边或弧相关的信息,如权值等。对于无权图,如果没有与边相关的其他信息,可省略此域。
每个链表设一个表头结点(称为顶点结点),由两个域组成,如图所示。链域(firstarc)指向链表中的第一个结点,数据域(data)存储顶点名或其他信息。
adjvex |
nextarc |
info |
|
data |
firstarc |
表节点 |
|
顶点节点 |
在图的邻接表中,所有顶点结点用一个向量以顺序结构形式存储,可以随机访问任意顶点的链表,该向量称为表头向量,向量的下标指示顶点的序号。
//图的邻接表存储表示 #define MAX_VERTEX_NUM 20 typedef struct ArcNode { int adjvex; //该弧所指向的顶点的位置 struct ArcNode *nextarc; //指向下一条弧 InfoType *info; //该弧相关信息的指针 }ArcNode; typedef struct VNode { VertexType data; //顶点信息 ArcNode *firstarc; //指向依附该顶点的第一条弧 }VNode,AdjList[MAX_VERTEX_NUM]; typedef struct { AdjList vertices; int vexnum,arcnum; //图的当前顶点数和弧数 int kind; //图的种类标志 }ALGraph;
2.2.2 示例
用邻接表存储图时,对无向图,其邻接表是唯一的,对有向图,其邻接表有两种形式,如图所示。
2.2.3 邻接表法的特点
a) 表头向量中每个分量就是一个单链表的头结点,分量个数就是图中的顶点数目;
b) 在边或弧稀疏的条件下,用邻接表表示比用邻接矩阵表示节省存储空间;
c) 在无向图,顶点Vi的度是第i个链表的结点数;
d) 若无向图中有n个顶点、e条边,则它的邻接表需n个头结点和2e个表结点。
e) 对有向图可以建立正邻接表或逆邻接表。正邻接表是以顶点Vi为弧尾而建立的邻接表;逆邻接表是以顶点Vi为弧头而建立的邻接表;
f) 在有向图的正邻接表中,第i个链表中的结点数是顶点Vi的出度,求入度须遍历整个邻接表;在有向图的逆邻接表中,第i个链表中的结点数是顶点Vi的入度,求出度须遍历整个邻接表;
g) 在邻接表上容易找出任一顶点的第一个邻接点和下一个邻接点;
2.3 十字链表
有向图的另外一种链式存储结构。可以看成是将有向图的正邻接表和逆邻接表结合起来得到的一种链表。在十字链表中,对应于有向图中每一条弧有一个结点,对应于每个顶点也有一个结点。
2.3.1 结点结构
tailvex |
headvex |
hlink |
tlink |
info |
|
data |
firstin |
firstout |
弧结点 |
|
顶点结点 |
弧结点:
尾域tailvex:指示弧尾顶点在图中的位置;
头域headvex:指示弧头顶点在图中的位置;
指针域hlink:指向弧头相同的下一条弧;
指针域tlink:指向弧尾相同的下一条弧;
Info域:指向该弧的相关信息。
弧头相同的弧在同一链表上,弧尾相同的弧在同一链表上。它们的头结点即为顶点结点。
顶点结点:(同邻接表,顶点结点为顺序存储结构)
data域:存储和顶点相关的信息;
指针域firstin:指向以该顶点为弧头的第一条弧结点;
指针域firstout:指向以该顶点为弧尾的第一条弧结点。
//有向图的十字链表存储表示 #define MAX_VERTEX_NUM 20 typedef struct ArcBox { int tailvex,headvex; //弧尾和弧头顶点的位置 struct ArcBox *hlink,*tlink; //分别指向弧头和弧尾相同的弧 InfoType *info; //该弧相关信息的指针 }ArcBox; typedef struct VexNode { VertexType data; //顶点信息 ArcBox *firstin,*firstout; //分别指向该顶点第一条入弧和出弧 }VexNode; typedef struct { VexNode xlist[MAX_VERTEX_NUM]; //表头向量 int vexnum,arcnum; //有向图的当前顶点数和弧数 }OLGraph;
2.3.2 示例
从这种存储结构图可以看出,从一个顶点结点的firstout出发,沿表结点的tlink指针构成了正邻接表的链表结构,而从一个顶点结点的firstin出发,沿表结点的hlink指针构成了逆邻接表的链表结构。
在十字链表中既容易求得以vi为尾的弧,也容易找到以vi为头的弧。因而容易求得顶点的出度和入度。
2.4 邻接多重表
邻接多重表(Adjacency Multilist)是无向图的另一种链式存储结构。
邻接表是无向图的一种有效的存储结构,在无向图的邻接表中,一条边(v,w)的两个表结点分别出现在以v和w为头结点的链表中,很容易求得顶点和边的信息,但在涉及到边的操作会带来不便。
邻接多重表的结构和十字链表类似,每条边用一个结点表示;每个顶点用一个结点表示。
2.4.1 结点结构
mark |
ivex |
ilink |
jvex |
jlink |
info |
|
data |
firstedge |
边结点 |
|
顶点结点 |
边结点:
标志域mark:用以标识该条边是否被访问过;
ivex和jvex域:该边所依附的两个顶点在图中的位置;
指针域ilink:指向下一条依附于顶点ivex的边;
指针域jlink:指向下一条依附于顶点jvex的边;
info域:保存该边的相关信息。
顶点节点:
Data域:存储和顶点相关的信息;
指针域firstedge:指向依附于该顶点的第一条边。
//无向图的邻接多重表存储表示 #define MAX_VERTEX_NUM 20 typedef enum {unvisited,visited} VisitIf; typedef struct EBox { VisitIf mark; //访问标记 int ivex,jvex; //该边依附的两个顶点的位置 struct EBox *ilink,*jlink; //分别指向依附这两个顶点的下一条边 InfoType *info; //该边相关信息的指针 } typedef struct VexBox { VertexType data; EBox *firstedge; //指向第一条依附该顶点的边 }VexBox; typedef struct { VexBox adjmulist[MAX_VERTEX_NUM]; int vexnum,edgenum; //无向图的当前顶点数和边数 }AMLGraph;
2.4.2 示例
在邻接多重表中,所有依附于同一顶点的边串联在同一链表中,由于每条边依附于两个顶点,则每个边结点同时链接在两个链表中。
邻接多重表与邻接表的区别:后者的同一条边用两个表结点表示,而前者只用一个表结点表示;除标志域外,邻接多重表与邻接表表达的信息是相同的,因此,操作的实现也基本相似。