1.09k likes | 1.26k Views
理解图的基本概念及有关术语,掌握图的四种存储结构的表示方法; 熟练掌握图的两种遍历(深度优先搜索和广度优先搜索),能列出按上述两种遍历算法得到的序列; 理解最小生成树的概念,能按 Prim 算法构造最小生成树; 掌握求最短路径、拓扑排序以及关键路径的算法思想。. 第七章 图. 学习要点. 7.1 图的的定义和术语. 图的定义. 图是由 一个 顶点集 V 和 一个描述顶点之间关系(边或者弧)的集合 R 构成的数据结构。 Graph = ( V , VR ) 其中, VR = {<v,w>| v,w∈V 且 P(v,w)}
E N D
理解图的基本概念及有关术语,掌握图的四种存储结构的表示方法;理解图的基本概念及有关术语,掌握图的四种存储结构的表示方法; 熟练掌握图的两种遍历(深度优先搜索和广度优先搜索),能列出按上述两种遍历算法得到的序列; 理解最小生成树的概念,能按Prim算法构造最小生成树; 掌握求最短路径、拓扑排序以及关键路径的算法思想。 第七章 图 学习要点
7.1 图的的定义和术语 • 图的定义 图是由一个顶点集 V和一个描述顶点之间关系(边或者弧)的集合R构成的数据结构。 Graph = ( V , VR ) 其中,VR={<v,w>| v,w∈V 且 P(v,w)} <v,w>表示从 v 到 w 的一条弧,并称 v 为弧尾或初始点,w 为弧头或终端点。 谓词 P(v,w) 定义了弧 <v,w>的意义或信息。
V0 V1 V2 V3 V1 V0 V2 V4 V3 • 图的应用举例 例1 交通图(公路、铁路) 顶点:地点 边:连接地点的公路 例2 电路图 顶点:元件 边:连接元件之间的线路 例3 各种流程图 如产品的生产流程图 顶点:工序 边:各道工序之间的顺序关系
V1 V0 V2 V4 V3 • 有向图和无向图 在图中,若用箭头标明了边是有方向性的,则称这样的图为有向图,否则称为无向图。 在无向图中,一条边(x,y)与(y,x)表示的结果相同,用圆括号表示。例如: G1=<V1, E1> V1={v0, v1, v2, v3, v4 } E1={(v0,v1),(v0,v3),(v1,v2),(v1,v4),(v2,v3),(v2,v4)}
V0 V1 V2 V3 • 有向图和无向图 在有向图中,一条边<x,y>与<y,x>表示的结果不相同,用尖括号表示。<x,y>表示从顶点x出发向顶点y的边,x为始点,y为终点。例如: G2=<V2, A2> V2={v0, v1, v2, v3} A2={<v0,v1 >, <v0,v2 >, <v2,v3 >, <v3,v0 >}
名词和术语 顶点、边、弧、弧头、弧尾 完全图、稠密图、稀疏图 度、入度、出度 边的权、网图 路径、路径长度 回路、简单路径、简单回路 子图 连通图、连通分量 强连通图、强连通分量 生成树、生成森林
W V • 顶点、边、弧、弧头、弧尾 图中的数据元素通常称为顶点; 若<v,w>∈VR,则<v,w>表示从v到w的一条弧,且称v为弧尾或初始点,称w为弧头或终端点。 若<v,w>∈VR,必有则<w,v>∈VR,即VR是对称的,则以无序对(v,w)代替这两个有序对,表示从v和w的一条边。 W V
2 2 1 3 1 3 • 完全图、稠密图、稀疏图 在一个无向图中,如果任意两顶点都有一条直接边相连接,则称该图为无向完全图。 在一个有向图中,如果任意两顶点之间都有方向互为相反的两条弧相连接,则称为有向完全图。 若一个图接近完全图,称为稠密图;称边数很少的图为稀疏图。 有向完全图 无向完全图
1 5 7 2 4 5 3 2 4 6 1 3 6 • 度、入度、出度 在图中,一个顶点依附的边或弧的数目,称为该顶点的度。在有向图中,以顶点V为起点的有向边数称为顶点V的出度,以顶点V为终点的有向边数称为顶点V的入度,入度和出度之和称为该顶点的度。
2 B A 1 4 1 3 3 4 1 5 5 5 6 3 2 8 C 4 2 7 • 边的权、网 与边有关的数据信息称为权。 边上带权的图称为网。 (b)有向网 (a) 无向网
V1 V1 V0 V2 V0 V4 V3 • 路径、路径长度 无向图中从顶点v到v`的路径是一个顶点序列(v=vi,0,vi,1,…,vi,m=v`),其中(vi,j-1,vi,j)∈E(G) 。在有向图中路径也是有向的,它是由若干条弧组成。路径上边或弧的数目称为路径长度。
V0 V1 V2 V3 V1 V0 V2 V4 V3 • 回路、简单路径、简单回路 起点和终点相同的路径称为回路或者环。序列中顶点不重复出现的路径称为简单路径。除第一个顶点与最后一个顶点之外,其他顶点不重复出现的回路称为简单回路。 无向图G1 有向图G2
V1 V1 V0 V0 V2 V4 V3 V3 V1 V0 V2 V4 V3 • 子图 若有两个图G和G`,G=(V,E),G`=(V`,E` ), 满足如下条件:V`V,E`E,即V`为V的子集, E`为E的子集,称图G`为图G的子图。 例 (b)、(c) 是 (a) 的子图 (a) (b) (c)
V0 V4 V3 V0 V2 V3 V5 V4 G2的两个连通分量 V2 V1 V1 • 连通图、连通分量 在无向图G中,如果从顶点v到顶点v`有路径,则称v和v`是连通的。若图中任意两个顶点都是连通的,则称G为连通图。 无向图中的极大连通子图称该图的连通分量。 连通图G1 非连通图G2
V3 V1 V0 V2 V2 V1 V3 V3 V2 V1 V0 V0 • 强连通图、强连通分量 在有向图G中,如果对于任意一对顶点vj和vi 均有从一个顶点vj到另一个顶点vi有路径,也有从vi到 vj的路径,则称该有向图是强连通图。 有向图的极大强连通子图称为强连通分量。 强连通图G1 非强连通图G2 G2的两个强连通分量
V0 V4 V3 V0 V4 V3 V0 V4 V3 V2 V2 V2 V1 V1 V1 • 生成树、生成森林 连通图G的生成树,是G的包含其全部n 个顶点的一个极小连通子图。它必定包含且仅包含G的n-1条边。 极小连通子图意思是:该子图是G 的连通子图,在该子图中删除任何一条边,子图不再连通。 非连通图的生成树则组成一个生成森林。若图中有n个顶点,m个连通分量,则生成森林中有n-m条边。 连通图 G1 G1的生成树
2 4 5 1 3 6 1 5 7 2 4 6 3 • 例: 路径:1,2,3,5,6,3 路径长度:5 简单路径:1,2,3,5 回路:1,2,3,5,6,3,1 简单回路:3,5,6,3 G1 路径:1,2,5,7,6,5,2,3 路径长度:7 简单路径:1,2,5,7,6 回路:1,2,5,7,6,5,2,1 简单回路:1,2,3,1 G2
7.2 图的存储表示 图是一种结构复杂的数据结构,表现在不仅各个顶点的度可以千差万别,而且顶点之间的逻辑关系也错综复杂。从图的定义可知,一个图的信息包括两部分,即图中顶点的信息以及描述顶点之间的关系(边或者弧)的信息。因此无论采用什么方法建立图的存储结构,都要完整、准确地反映这两方面的信息。 图通常有邻接矩阵、邻接表、邻接多重表和十字链表等表示方法。
1 若(vi,vj)或<vi,vj>是E(G)中的边 0 若(vi,vj)或<vi,vj>不是E(G)中的边 A[i][j]= 7.2.1 邻接矩阵 定义:在邻接矩阵表示中,除了用一维数组存放顶点本身信息外,还用一个矩阵表示各个顶点之间的邻接关系。这个矩阵称为邻接矩阵。 假设图G=(V, E)有n个确定的顶点,即V={v0,v1,…, vn-1} ,则表示G中各顶点相邻关系为一个n×n的矩阵,矩阵的元素为 :
V0 V1 V2 V3 V1 V0 V2 V3 V4 7.2.1 邻接矩阵 例 A= B=
5 V3 V0 7 6 8 V4 3 V1 V2 9 4 7.2.1 邻接矩阵 若G是带权图,则邻接矩阵定义为: wij若(vi,vj)或<vi,vj>是E(G)中的边 ∞ 若(vi,vj)或<vi,vj>不是E(G)中的边 其中 wij表示边上的权值; ∞表示大于所有边上权值的数。 A[i][j]= C=
7.2.1 邻接矩阵 从无向图的邻接矩阵可以得出如下结论: • 矩阵是对称的; • 第i行或第i 列1的个数为顶点i 的度; • 矩阵中1的个数的一半为图中边的数目; • 很容易判断顶点i和顶点j之间是否有边相连(看矩阵中i行j列值是否为1)。
7.2.1 邻接矩阵 从有向图的邻接矩阵可以得出如下结论: • 矩阵不一定是对称的; • 第i 行中1的个数为顶点i 的出度; • 第i列中1的个数为顶点 i的入度; • 矩阵中1的个数为图中弧的数目; • 很容易判断顶点i和顶点j是否有弧相连。
7.2.1 邻接矩阵 图的邻接矩阵数据类型描述: 在用邻接矩阵存储图时,除了用一个二维数组存储用于表示顶点间相邻关系的邻接矩阵外,还需用一个一维数组来存储顶点信息,另外还有图的顶点数和边数。故可将其形式描述如下: 简化后的邻接矩阵的存储表示: #define INFINITY 1000 #define MAX_VERTEX_NUM 20
7.2.1 邻接矩阵 typedef char VertexType; // 顶点的定义 typedef int VRType; // 弧的定义 typedef struct { VertexType vexs[MAX_VERTEX_NUM]; // 顶点信息 VRType arcs [MAX_VERTEX_NUM] [MAX_VERTEX_NUM]; // 边的信息 int vexnum, arcnum; // 顶点数,弧数 } MGraph;
7.2.2 邻接表 邻接表是图的一种顺序存储与链式存储结合的存储方法。邻接表表示法类似于树的孩子链表表示法。 就是对于图G中的每个顶点vi,将所有邻接于vi的顶点链成一个单链表,这个单链表就称为顶点vi的邻接表,再将所有的邻接表表头放到数组中,就构成了图的邻接表。其中,单链表中的结点称为表结点,每个单链表设的一个头结点称为顶点结点。
一、无向图的邻接表 对图中每个顶点Vi建立一个单链表,链表中的结点表示依附于顶点Vi的边,每个链表结点为两个域: 每个链表附设一个头结点,头结点结构为: 其中:顶点域data存放顶点信息; 表头指针firstarc指向链表的第一个结点。
4 4 0 1 3 0 V1 V0 1 3 2 V2 2 V3 V4 1 2 顶点:通常按编号顺序将顶点数据存储在一维数组中; 与同一顶点关联的边:用线性链表存储 下标 编号 link 特点:设无向图中顶点数为n,边数为e, 则它的邻接表需要n个头结点和2e个表结点。 顶点Vi的度 TD(Vi)=链表i中的表结点数。 返回
图的邻接表数据类型描述: #define MAX_VER_NUM 20 typedef struct ArcNode{ int adjvex; struct ArcNode *next; }ArcNode; typedef struct VNode{ VertexType data; EdgeNode *firstedge; }Vnode,AdjList[MAX_VERTEX_NUM]; typedef struct{ AdjList vertices; int vexnum,arcnum; }ALGraph;
0 V0 V1 V2 V3 1 2 3 二、有向图邻接表 n个顶点,e条弧的有向图,需n个表头结点,e个表结点。 用链表存储同一顶点为起点的弧 在有向图中,第i个链表中的结点个数是顶点vi的出度。要求某个结点的入度,必须遍历整个邻接表。在所有链表中其邻接点域的值为i的结点的个数是顶点vi的入度。
2 V0 V1 V2 V3 3 0 0 三、有向图的逆邻接表 为了便于确定顶点的入度或以顶点vi为头的弧,可以建立一个有向图的逆邻接表,即对每个顶点vi建立一个以vi为头的弧尾结点组成的链表。 用链表存储同一顶点为起点的弧
四、 结论 • 对于无向图的邻接表来说,一条边对应两个单链 表结点,邻接表结点总数是边数的2倍。 • 在无向图的邻接表中,各顶点对应的单链表的结 点数(不算表头结点)就等于该顶点的度数。 • 在有向图邻接表中,一条弧对应一个表结点,表 结点的数目和弧的数目相同。 • 在有向图邻接表中,单链表的结点数就等于相应 顶点的出度数。
要求有向图中某顶点的入度数,需扫视邻接表的 所有单链表,统计与顶点标号相应的结点个数。 • 建立的邻接表不是唯一的,与键盘输入边的顺序 有关,输入的边的顺序不同,则得到的链表也不 同。 • 在邻接表中容易找任一顶点的第一个邻接点和下 一个邻接点,但要判定任意两顶点之间是否有边 或弧,则需搜索第i个或第j个链表,不及邻接矩 阵方便。
7.2.3 十字链表 十字链表是将有向图的邻接表和逆邻接表结合起来的一种有向图链式存储结构。 结点结构 有向图的每一条弧有一个弧结点,每一个顶点必有一个顶点结点,其结构为: 弧结点 顶点结点
有向图的十字链表存储结构: #define MAX_VERTEX_NUM 20 typedef struct ArcBox { int tailvex,headvex; struct ArcBox *hlink,tlink; }ArcBox; typedef struct VexNode { VertexType data; ArcBox *fisrtin, firstout; }VexNode; typedef struct { VexNode xlist[MAX_VERTEX_NUM]; int vexnum,arcnum; }OLGraph;
2 1 3 4 1 2 1 3 1 1 2 2 3 3 3 1 3 4 4 4 4 3 4 1 4 2 特点: ① 顶点结点数=顶点数 ; 弧结点数=弧的条数 ② 求入度:从顶点Vi出发,沿着hlink所经过的弧结点数 求出度:从顶点Vi出发,沿着tlink所经过的弧结点数 hlink tlink ^ ^ ^ ^ ^ ^ ^ ^
mark jlink ivex ilink jvex data firstedge 7.2.4 邻接多重表 邻接多重表是无向图的另一种链式存储结构。 每一个顶点有一个顶点结点,顶点结点结构为: 图的每一条边有一个边结点,边结点的结构为:
2 1 3 2 5 2 3 5 3 4 1 4 1 2 1 1 2 2 3 3 3 4 5 4 4 5 5 ^ ^ ^ ^ ^ 特点:顶点结点数为n,边结点数为e; 对需要得到边的两个顶点的一类操作很方便
图的邻接多重表数据类型描述: #define MAX_VERTEX_NUM 20 typedef emnu{ unvisited,visited} VisitIf; typedef struct EBox{ VisitIf mark; int ivex,jvex; struct EBox *ilink, *jlink; }EBox; typedef struct VexBox{ VertexType data; EBox *fistedge; }VexBox; typedef struct{ VexBox adjmulist[MAX_VERTEX_NUM]; int vexnum,edgenum; }AMLGraph; /*访问标记*/ /*该边依附的两个顶点的位置*/ /*指向依附这两个顶点的下一条边*/ /*指向第一条依附该顶点的边*/ /*无向图的当前顶点数和边数*/
在不同的存储结构下,实现各种操作的效率可能是不同的。所以在求解实际问题时,要根据求解问题所需操作,选择合适的存储结构。在不同的存储结构下,实现各种操作的效率可能是不同的。所以在求解实际问题时,要根据求解问题所需操作,选择合适的存储结构。
7.3 图的遍历 和树的遍历类似,图的遍历也是从某个顶点出发,沿着某条搜索路径对图中所有顶点各作一次访问。图访问的四个难点: 首结点 、非连通图 、回路、多个相连顶点 我们可以设置一个全局型标志数组visited来标志某个顶点是否被访过,未访问的值为0,访问过的值为1。 根据搜索路径的方向不同,图的遍历有两种方法:深度优先搜索和广度优先搜索。
7.3.1 深度优先搜索 深度优先搜索遍历类似于树的先序遍历。假定给定图G的初态是所有顶点均未被访问过,在G中任选一个顶点v作为遍历的初始点,则深度优先搜索遍历可定义如下: • 首先访问顶点v,并将其访问标记置为访问过,即visited[v]=TRUE ; • 然后搜索与顶点v有边相连的下一个顶点w,若w未被访问过,则访问它,并将w的访问标记置为访问过,然后从w开始重复此过程;若w已访问,再访问与v有边相连的其它顶点; • 若与v有边相连的顶点都被访问过,则退回到前一个访问顶点并重复刚才过程,直到图中所有顶点都被访问完止。 • 深度优先搜索是一种递归的过程
V0 V7 V6 V5 V4 V3 V2 V1 深度优先遍历 从图中某顶点v出发: • 访问顶点v; • 依次从v的未被访问的邻接点出发,对图进行深度优先遍历; 先序遍历(DLR) 若二叉树非空 如果将图顶点的邻接点看作二叉树结点的左、右孩子深度优先遍历与先序遍历是不是很类似? • 访问根结点; • 先序遍历左子树; • 先序遍历右子树;
V1 V2 V3 V4 V5 V6 V7 V8 V1 V2 V3 V4 V5 V6 V7 V8 深度遍历:V1 V2 V4 V8 V5 V6 V3 V7 由于没有规定访问邻接点的顺序,深度优先序列不是唯一的 深度遍历:V1 V2 V4 V8 V3 V6 V7 V5
对某顶点的深度优先遍历的递归算法: Boolean visited[MAX]; void DFSTraverse(Graph G,int v){ for(v=0;v<G.vexnum;++v) visited[v]=FALSE; for(v=0;v<G.vexnum;++v) if(!visited[v]) DFS(G,v); } //访问标志数组 //标志数组初始化 //依次对数组中的每个未访问的顶点调用DFS搜索
以v为起点对G进行DFS搜索 void DFS (Graph G,int v){ visited[v]=TRUE; for(w=FirstAdjVex(G,v);w>=0; w=NextAdjVex(G,v,w)) if (!visited[w]) DFS (G,w); } //访问顶点v //对v的尚未访问过的邻 接点w递归调用DFS
V0 V1 V2 V3 V4 V5 V6 V7 0 1 2 3 4 5 6 7 4 0 7 1 2 3 0 5 6 1 6 7 3 2 2 4 1 5 V1 V7 V6 V5 V2 V3 V4 V0 深度遍历:V0 V1 V3 V7 V4 V2 V5 V6 V0 V1 V2 V3 V4 V5 V6 V7
深度优先搜索的算法复杂度: 在遍历时,对图中每个顶点至多调用一次DFS 函数,因为一旦某个顶点被标志成已被访问,就不再从它出发进行搜索。因此,遍历图的过程实质上是对每个顶点查找其邻接点的过程。其耗费的时间则取决于所采用的存储结构。
0 1 2 3 当用二维数组表示邻接矩阵图的存储结构时,查找每个顶点的邻接点所需时间为O(n2),其中n为图中顶点数。 而当以邻接表作图的存储结构时,找邻接点所需时间为O(e),其中e为无向图中边的数或有向图中弧的数。由此,当以邻接表作存储结构时,深度优先搜索遍历图的时间复杂度为O(n+e)。
V1 V2 V3 V4 V5 V6 V7 V8 7.3.2 广度优先搜索 广度优先搜索遍历类似于树的按层次遍历。设图G的初态是所有顶点均未访问,在G 中任选一顶点v作为初始点,则广度优先搜索的基本思想是: • 首先访问顶点v,并将其访问标志置为已被访问,即visited[v]=TRUE ; • 接着依次访问与顶点v有边相连的所有顶点W1,W2,…,Wt; • 然后再按顺序访问与W1,W2,…,Wt有边相连又未曾访问过的顶点;依此类推,直到图中所有顶点都被访问完为止 。 即广度优先搜索遍历图的过程中以v为起始点,由近至远,依次访问和v有路径相通且路径长度为1,2,…的顶点。