文章目录
图的存储及基本操作
考纲内容
复习提示
1.邻接矩阵法
2.邻接表法
3.十字链表
4.邻接多重表
5.图的基本操作
图的存储及基本操作
图的存储必须要完整、准确地反映顶点集和边集的信息。根据不同图的结构和算法,采用不同的存储方式将对程序的效率产生相当大的影响,因此所选的存储结构应适合于待求解的问题。
考纲内容
(一)图的基本概念
(二)图的存储及基本操作
邻接矩阵;邻接表;邻接多重表;十字链表
(三)图的遍历
深度优先搜索;广度优先搜索
(四)图的基本应用
最小(代价)生成树;最短路径;拓扑排序;关键路径
复习提示
图算法的难度较大,主要掌握深度优先搜索与广度优先搜索。掌握图的基本概念及基本性质、图的存储结构(邻接矩阵、邻接表、邻接多重表和十字链表)及特性、存储结构之间的转化、基于存储结构上的各种遍历操作和各种应用(拓扑排序、最小生成树、最短路径和关键路径)等。
图的相关算法较多,通常只需掌握其基本思想和实现步骤,而实现代码不是重点。
1.邻接矩阵法
所谓邻接矩阵存储,是指用一个一维数组存储图中顶点的信息,用一个二维数组存储图中边的信息(即各顶点之间的邻接关系),存储顶点之间邻接关系的二维数组称为邻接矩阵。
顶点数为n的图 G=(V,E)的邻接矩阵A是nxn的,将G的顶点编号为,则
【命题追踪——图的邻接矩阵存储及相互转换】
对带权图而言,若顶点和之间有边相连,则邻接矩阵中对应项存放着该边对应的权值,若顶点和不相连,则通常用0或∞来代表这两个顶点之间不存在边:
有向图、无向图和网对应的邻接矩阵示例如图 6.5 所示。
【命题追踪——(算法题)邻接矩阵的遍历及顶点的度的计算】
图的邻接矩阵存储结构定义如下:
#define MaxVertexNum 100 //顶点数目的最大值
typedef char VertexType; //顶点对应的数据类型
typedef int Edgerype; //边对应的数据类型
typedef struct{
VertexType vex[MaxVertexNum]; //顶点表
EdgeType edge[MaxVertexNum][MaxVertexNum];//邻接矩阵,边表
int vexnum,arcnum; //图的当前顶点数和边数
}MGraph;
注意:
① 在简单应用中,可直接用二维数组作为图的邻接矩阵(顶点信息等均可省略)。
② 当邻接矩阵的元素仅表示相应边是否存在时,EdgeType 可用值为0和1的枚举类型。③ 无向图的邻接矩阵是对称矩阵,对规模特大的邻接矩阵可采用压缩存储。
④ 邻接矩阵表示法的空间复杂度为 O(n²),其中n为图的顶点数|V|。
【命题追踪——邻接矩阵的遍历的时间复杂度】
图的邻接矩阵存储表示法具有以下特点:
① 无向图的邻接矩阵一定是一个对称矩阵(并且唯一)。因此,在实际存储邻接矩阵时只需存储上(或下)三角矩阵的元素。
【命题追踪——基于邻接矩阵的顶点的度的计算】
② 对于无向图,邻接矩阵的第i行(或第i列)非零元素(或非∞元素)的个数正好是顶点i的度 。
③ 对于有向图,邻接矩阵的第i行非零元素(或非∞元素)的个数正好是顶点i的出度;
第i列非零元素(或非∞元素)的个数正好是顶点i的入度。
④ 用邻接矩阵存储图,很容易确定图中任意两个顶点之间是否有边相连。但是,要确定图中有多少条边,则必须按行、按列对每个元素进行检测,所花费的时间代价很
大。
⑤ 稠密图(即边数较多的图)适合采用邻接矩阵的存储表示。
【命题追踪——计算A²并说明的含义】
⑥ 设图 G 的邻接矩阵为 A,的元素 等于由顶点i到顶点j的长度为 n的路径的数目。该结论了解即可,证明方法可参考离散数学教材。
2.邻接表法
当一个图为稀疏图时,使用邻接矩阵法显然会浪费大量的存储空间,而图的邻接表法结合了顺序存储和链式存储方法,大大减少了这种不必要的浪费。
所谓邻接表,是指对图 G中的每个顶点建立一个单链表,第i个单链表中的结点表示依附于顶点的边(对于有向图则是以顶点为尾的弧),这个单链表就称为顶点的边表(对于有向图则称为出边表)。
边表的头指针和顶点的数据信息采用顺序存储,称为顶点表,所以在邻接表中存在两种结点:顶点表结点和边表结点,如图6.6所示。
顶点表结点由两个域组成:顶点域(data)存储顶点的相关信息,边表头指针域(firstarc)指向第一条边的边表结点。
边表结点至少由两个域组成:邻接点域(adjvex)存储与头结点顶点邻接的顶点编号,指针域(nextarc)指向下一条边的边表结点。
【命题追踪——图的邻接表存储的应用】
无向图和有向图的邻接表的实例分别如图6.7和图6.8所示,
图的邻接表存储结构定义如下:
#define MaxVertexNum 100 //图中顶点数目的最大值
typedef struct ArcNode( //边表结点
int adjvex; //该弧所指向的顶点的位置
struct ArcNode *nextarc; //指向下一条弧的指针
//InfoType info; //网的边权值
}ArcNode;
typedef struct VNode{ //顶点表结点
VertexType data; //顶点信息
ArcNode *firstarc; //指向第一条依附该顶点的弧的指针
}VNode,AdjList[MaxVertexNum】;
typedef struct{
AdjList vertices; //邻接表
int vexnumarcnum; //图的顶点数和弧数
}ALGraph; //ALGraph是以邻接表存储的图类型
图的邻接表存储方法具有以下特点:
① 若 G为无向图,则所需的存储空间为 O(|V|+2|E|);若 G为有向图,则所需的存储空间为O(|V|+|E|)。
前者的倍数2是因为在无向图中,每条边在邻接表中出现了两次。
【命题追踪——邻接矩阵法和邻接表法的适用性差异】
② 对于稀疏图(即边数较少的图),采用邻接表表示将极大地节省存储空间。
③ 在邻接表中,给定一个顶点,能很容易地找出它的所有邻边,因为只需要读取它的邻接表。
在邻接矩阵中,相同的操作则需要扫描一行,花费的时间为 O(n)。
但是,若要确定给定的两个顶点间是否存在边,则在邻接矩阵中可以立刻查到,而在邻接表中则需要在相应结点对应的边表中查找另一结点,效率较低。
④ 在无向图的邻接表中,求某个顶点的度只需计算其邻接表中的边表结点个数。
在有向图的邻接表中,求某个顶点的出度只需计算其邻接表中的边表结点个数;
但求某个顶点x的入度则需遍历全部的邻接表,统计邻接点(adjvex)域为x的边表结点个数。
⑤ 图的邻接表表示并不唯一,因为在每个顶点对应的边表中,各边结点的链接次序可以是任意的,它取决于建立邻接表的算法及边的输入次序。
3.十字链表
十字链表是有向图的一种链式存储结构。在十字链表中,有向图的每条弧用一个结点(弧结点)来表示,每个顶点也用一个结点(顶点结点)来表示。
两种结点的结构如下所示。
弧结点中有5个域:
- tailvex和 headvex 两个域分别指示弧尾和弧头这两个顶点的编号;
- 头链域 hlink 指向弧头相同的下一个弧结点;
- 尾链域 tlink 指向弧尾相同的下一个弧结点;
- info 域存放该弧的相关信息。
这样,弧头相同的弧在同一个链表上,弧尾相同的弧也在同一个链表上。
顶点结点中有3 个域:
- data 域存放该顶点的数据信息,如顶点名称;
- firstin 域指向以该顶点为弧头的第一个弧结点;
- firstout 域指向以该顶点为弧尾的第一个弧结点。
图 6.9为有向图的十字链表表示法。
注意,顶点结点之间是顺序存储的,弧结点省略了 info 域。
在十字链表中,既容易找到Vi 为尾的弧,也容易找到Vi为头的弧,因而容易求得顶点的出度和入度。
图的十字链表表示是不唯一的,但一个十字链表表示唯一确定一个图。
4.邻接多重表
邻接多重表是无向图的一种链式存储结构。在邻接表中,容易求得顶点和边的各种信息,但在邻接表中求两个顶点之间是否存在边而对边执行删除等操作时,需要分别在两个顶点的边表中遍历,效率较低。
与十字链表类似,在邻接多重表中,每条边用一个结点表示,其结构如下所示。
其中,ivex 和 jvex 这两个域指示该边依附的两个顶点的编号;
ilink 域指向下一条依附于顶点 ivex 的边;
jlink 域指向下一条依附于顶点 jvex 的边,info 域存放该边的相关信息。
每个顶点也用一个结点表示,它由如下所示的两个域组成。
其中,data 域存放该顶点的相关信息,firstedge 域指向第一条依附于该顶点的边。
在邻接多重表中,所有依附于同一顶点的边串联在同一链表中,因为每条边依附于两个顶点,所以每个边结点同时链接在两个链表中。
对无向图而言,其邻接多重表和邻接表的差别仅在于,同一条边在邻接表中用两个结点表示,而在邻接多重表中只有一个结点。
图 6.10为无向图的邻接多重表表示法。邻接多重表的各种基本操作的实现和邻接表类似。
图的四种存储方式的总结如表 所示。
邻接矩阵 | 邻接表 | 十字链表 | 邻接多重表 | |
空间复杂度 | O(|V|²) | 无向图:O(|V|+2|E|) 有向图:O(|V|+|E|) | O(|V|+|E|) | O(|V|+|E|) |
找相邻边 | 遍历对应行或列的时间复杂度为O(|V|) | 找有向图的入度必须遍历整个邻接表 | 很方便 | 很方便 |
删除边或顶点 | 删除边很方便,删除顶点需要大量移动数据 | 无向图中删除边或顶点都不方便 | 很方便 | 很方便 |
适用于 | 稠密图 | 稀疏图和其他 | 只能存有向图 | 只能存无向图 |
表示方法 | 唯一 | 不唯一 | 不唯一 | 不唯一 |
5.图的基本操作
图的基本操作是独立于图的存储结构的。而对于不同的存储方式,操作算法的具体实现会有着不同的性能。
在设计具体算法的实现时,应考虑采用何种存储方式的算法效率会更高。
图的基本操作主要包括(仅抽象地考虑,所以忽略各变量的类型):
- Adjacent(G,x,y):判断图 G 是否存在边<x,y>或(x,y)。
- Neighbors(G,x):列出图G中与结点x邻接的边。
- InsertVertex(G,x):在图G中插入顶点x。
- DeleteVertex(G,x):从图G中删除顶点x。
- AddEdge(G,x,y):若无向边(x,y)或有向边<x,y>不存在,则向图G中添加该边。
- RemoveEdge(G,x,y):若无向边(x,y)或有向边<x,y>存在,则从图G中删除该边
- FirstNeighbor(G,x):求图G中顶点 x的第一个邻接点,若有则返回顶点号。若x没有邻接点或图中不存在x,则返回 -1。
- NextNeighbor(G,x,y):假设图G 中顶点y是顶点x的一个邻接点,返回除y外顶点x的下一个邻接点的顶点号,若y是x的最后一个邻接点,则返回-1。
- Get_edge_value(G,x,y):获取图 G 中边(x,y)或<x,y>对应的权值。
- Set_edge_value(G,x,y,v):设置图G 中边(x,y)或<x,y>对应的权值为 v。
此外,还有图的遍历算法:按照某种方式访问图中的每个顶点且仅访问一次。
图的遍历算法包括深度优先遍历和广度优先遍历,具体见下一节的内容。