1、第七章 图,本章主要内容:图的基本概念图的邻接矩阵、邻接表和边集数组表示图的深度优先搜索和广度优先搜索遍历方法求图的最小生成树的普里姆算法和克鲁斯卡尔算法有向图的拓扑排序等内容。,图是一种比线性表和树都复杂的数据结构。在线性表中每个元素(除了开始和结束)只有一个直接的前驱和一个直接的后继;在树型结构中,元素之间有着明显的层次关系,而每一层元素都只与它临近层上的元素有关系,而且是上层元素和下层的多个元素有关系;在图结构中,节点之间的关系是任意的,图中任意两个元素之间都可能有关系。图的应用非常广泛。,7.1 图的抽象数据类型定义,图是由一个顶点集 V 和一个弧集 R构成的数据结构。 ADT Gra
2、ph 数据对象: V是具有相同特性的数据元素的集合,称为顶点集; 数据关系:R=VR,VR| v,wV 且 P(v,w)表示从 v 到 w 的一条弧,并称 v 为弧尾,w 为弧头。谓词 P(v,w) 定义了弧 的意义或信息。,图的结构定义:,结构的建立和销毁,插入或删除顶点,对邻接点的操作,对顶点的访问操作,遍历,插入和删除弧,基本操作(6种),CreatGraph(&G, V, VR): / 按定义(V, VR) 构造图,DestroyGraph(&G): / 销毁图,结构的建立和销毁,对顶点的访问操作,LocateVex(G, u); / 若G中存在顶点u,则返回该顶点在 / 图中“位置”
3、 ;否则返回其它信息。,GetVex(G, v); / 返回 v 的值。,PutVex( / 对 v 赋值value。,对邻接点的操作,FirstAdjVex(G, v); / 返回 v 的“第一个邻接点” 。若该顶点/在 G 中没有邻接点,则返回“空”。,NextAdjVex(G, v, w); / 返回 v 的(相对于 w 的) “下一个邻接/ 点”。若 w 是 v 的最后一个邻接点,则/ 返回“空”。,插入或删除顶点,InsertVex( /在图G中增添新顶点v。,DeleteVex( / 删除G中顶点v及其相关的弧。,插入和删除弧,InsertArc( /在G中增添弧,若G是无向的,
4、/则还增添对称弧。,DeleteArc( /在G中删除弧,若G是无向的, /则还删除对称弧。,遍 历,DFSTraverse(G, v, Visit(); /从顶点v起深度优先遍历图G,并对每/个顶点调用函数Visit一次且仅一次。,BFSTraverse(G, v, Visit(); /从顶点v起广度优先遍历图G,并对每/个顶点调用函数Visit一次且仅一次。,由于“弧”是有方向的,因此称由顶点集和弧集构成的图为有向图。,AB E C D,例如:,G1 = (V1, VR1),其中V1=A, B, C, D, EVR1=, , , , , , ,若VR 且VR,则称 (v,w) 为顶点v 和
5、顶点 w 之间存在一条边。,B CA D F E,由顶点集和边集构成的图称作无向图。,例如: G2=(V2,VR2)V2=A, B, C, D, E, FVR2=(A,B), (A,E), (B,E), (C,D), (D,F), (B,F), (C,F) ,名词和术语,网、子图,完全图、稀疏图、稠密图,邻接点、度、入度、出度,路径、路径长度、简单路径、简单回路,连通图、连通分量、强连通图、强连通分量,生成树、生成森林,弧或边带权的图分别称作有向网或无向网。(权表示一定的含义),1.网,B,设图G=(V,VR) 和图 G=(V,VR),且 VV, VRVR,则称 G 为 G 的子图。,2.子图
6、,图,三个子图,假设图中有 n 个顶点,e 条边,则,含有 e=n(n-1)/2 条边的无向图称作完全图;,含有 e=n(n-1) 条弧的有向图称作有向完全图;,若边或弧的个数 ekind); switch (G-kind) case DG : return CreateDG(G); case DN : return CreateDN(G); case UDG : return CreateUDG(G); case UDN : return CreateUDN(G); default : return ERROR; ,Status CreateUDN(MGraph *G) /建立无向网 sca
7、nf(G- vexnum, G-arcnum, ,for (k=0;karcnum;k+) scanf( /for /END,二、无向图的邻接表存储表示,1 3,2,3,2,0 1,0 1 2 3 4,A B C D E,有向图的邻接表,A,B,D,C,E,可见,在有向图的邻接表中不易找到指向该顶点的弧(即射入弧)。,A,B,E,C,D,有向图的逆邻接表,A B C D E,3,0,3,4,2,0,01234,在有向图的逆邻接表中,对每个顶点,链接的是指向该顶点的弧。,1,typedef struct AdjList vertices; int vexnum, arcnum; int kind
8、; / 图的种类标志 ALGraph;,图的结构定义,typedef struct VNode VertexType data; / 顶点信息 ArcNode *firstarc; / 指向第一条依附该顶点的弧 VNode, AdjListMAX_VERTEX_NUM;,data firstarc,顶点的结点结构,typedef struct ArcNode int adjvex; /该弧所指向的顶点的位置 struct ArcNode *nextarc; / 指向下一条弧的指针 InfoType *info; / 该弧相关信息的指针 ArcNode;,adjvex nextarc,弧的结点结
9、构,info,vexnumarcnumkind,vertices,adjvex nextarc,data firstarc,Status CreateGraph(ALGraph *G) /建立图的邻接表的结构 scanf(G-kind); switch (G-kind) case DG : return CreateDG(G); case DN : return CreateDN(G); case UDG : return CreateUDG(G); case UDN : return CreateUDN(G); default : return ERROR; ,Status CreateDN
10、(ALGraph *G) /建立无向网或无向图 scanf(G- vexnum, G-arcnum, ,for (k=0;karcnum;k+) arcn=(ArcNode *)malloc(sizeof(ArcNode); scanf( return OK; /end,A,B,D,C,E,假设边的输入顺序为:,A, D,A, B,B, C,D, C,E, B,E, A,C, E,typedef struct VexNode xlistMAX_VERTEX_NUM; / 顶点结点(表头向量) int vexnum, arcnum; /有向图的当前顶点数和弧数 OLGraph;,三、有向图的十字
11、链表存储表示,顶点的结点结构,顶点信息数据,指向该顶点的第一条入弧,指向该顶点的第一条出弧,typedef struct VexNode / 顶点的结构表示 VertexType data; ArcBox *firstin, *firstout; VexNode;,弧的结点结构,弧尾顶点位置 弧头顶点位置 弧的相关信息,指向下一个有相同弧头的结点,指向下一个有相同弧尾的结点,typedef struct ArcBox / 弧的结构表示 int tailvex, headvex; struct ArcBox *hlink, *tlink; InfoType *info; ArcBox;,三、有向
12、图的十字链表存储表示,Status CreateDG(OLGraph *G) scanf(G- vexnum, G-arcnum, ,for(k=0;karcnum;k+) scanf( /END 也按到序插入,typedef struct / 邻接多重表 VexBox adjmulistMAX_VERTEX_NUM; int vexnum, edgenum; AMLGraph;,无向图的结构表示,四、无向图的邻接多重表存储表示,顶点的结构表示,typedef struct VexBox VertexType data; EBox *firstedge; / 指向第一条依附该顶点的边 VexB
13、ox;,typedef struct Ebox VisitIf mark; / 访问标记 int ivex, jvex; /该边依附的两个顶点的位置 struct EBox *ilink, *jlink; /分别指向i,j顶点的下一条边 InfoType *info; / 该边信息指针 EBox;,边的结构表示,vexnum=5edgenum=6,7.3 图的遍历,从图中某个顶点出发游历图,访遍图中其余顶点,并且使图中的每个顶点仅被访问一次的过程。,深度优先搜索,广度优先搜索,遍历应用举例,从图中某个顶点V0 出发,访问此顶点,然后依次从V0的各个未被访问的邻接点出发深度优先搜索遍历图,直至图
14、中所有和V0有路径相通的顶点都被访问到。,一、深度优先搜索遍历图,连通图的深度优先搜索遍历,V,w1,SG1,SG2,SG3,W1、W2和W3 均为 V 的邻接点,SG1、SG2 和 SG3 分别为含顶点W1、W2和W3 的子图。,访问顶点 V :for (W1、W2、W3 ) 若该邻接点W未被访问, 则从它出发进行深度优先搜索遍历。,w2,w3,w2,从上页的图解可见:,从深度优先搜索遍历连通图的过程类似于树的先根遍历;,解决的办法是:为每个顶点设立一个 “访问标志 visitedw”。,2. 如何判别V的邻接点是否被访问?,一、深度优先搜索遍历图,Boolean visitedMAX;St
15、atus(* VisitFunc)(int v); /printf(v)void DFS(Graph G, int v) / 从顶点v出发,深度优先搜索遍历连通图 G visitedv = TRUE; printf(v); for(w=FirstAdjVex(G, v);w!=0;w=NextAdjVex(G,v,w) if (!visitedw) DFS(G, w); / 对v的尚未访问的邻接顶点w递归调用DFS / DFS,Void DFSTraverse(Graph G) for(v=0;vG.vexnum;+v) visitedv=false; for(v=0;vG.vexnum;+v
16、) if(!visitedv) DFS(G,V);,一、深度优先搜索遍历图,a,b,c,h,d,e,k,f,g,F F F F F F F F F,T,T,T,T,T,T,T,T,T,a,c,h,d,k,f,e,a,c,h,k,f,e,d,b,g,访问标志:,访问次序:,例如:,0 1 2 3 4 5 6,a,b,c,d,e,f,g,h,k,c,d,e,f,g,h,a,a,h,a,k,a,h,k,b,c,d,k,f,f,e,h,首先将图中每个顶点的访问标志设为 FALSE, 之后搜索图中每个顶点,如果未被访问,则以该顶点为起始点,进行深度优先搜索遍历,否则继续检查下一顶点。,非连通图的深度优先
17、搜索遍历,一、深度优先搜索遍历图,void DFSTraverse(Graph G, Status (*Visit)(int v) / 对图 G 作深度优先遍历。 VisitFunc = Visit; for (v=0; vG.vexnum; +v) visitedv = FALSE; / 访问标志数组初始化 for (v=0; vw1, V-w2, V-w8 的路径长度为1;,V-w7, V-w3, V-w5 的路径长度为2;,V-w6, V-w4 的路径长度为3。,w1,V,w2,w7,w6,w3,w8,w5,w4,从图中的某个顶点V0出发,并在访问此顶点之后依次访问V0的所有未被访问过的
18、邻接点,之后按这些顶点被访问的先后次序依次访问它们的邻接点,直至图中所有和V0有路径相通的顶点都被访问到。,若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。,void BFSTraverse(Graph G, Status (*Visit)(int v) for (v=0; vG.vexnum; +v) visitedv = FALSE; /初始化访问标志 InitQueue(Q); / 置空的辅助队列Q for ( v=0; vG.vexnum; +v ) if ( !visitedv) / v 尚未访问 / BFSTrave
19、rse, ,visitedv = TRUE; Visit(v); / 访问vEnQueue(Q, v); / v入队列while (!QueueEmpty(Q) DeQueue(Q, v); / 队头元素出队并置为u for(w=FirstAdjVex(G, v); w!=0; w=NextAdjVex(G,v,w) if ( ! visitedw) visitedw=TRUE; Visit(w); EnQueue(Q, w); / 访问的顶点w入队列 / if / while,a,b,c,h,d,e,k,f,g,F F F F F F F F F,T,T,T,T,T,T,T,T,T,a,c,
20、d,e,f,h,k,b,g,a,c,h,k,f,e,d,b,g,访问标志:,访问次序:,例如:,a b c d e f g h k,a,b,c,d,e,f,g,h,k,c,d,e,f,g,h,a,a,h,a,k,a,h,k,b,c,d,k,f,f,e,h,三、遍历应用举例,1. 求一条从顶点 i 到顶点 s 的简单路径,2. 求两个顶点之间的一条路径长度最短的路径,1. 求一条从顶点 i 到顶点 s 的简单路径,a,b,c,h,d,e,k,f,g,求从顶点 b 到顶点 k 的一条简单路径。,从顶点 b 出发进行深度优先搜索遍历。,例如:,假设找到的第一个邻接点是a,则得到的结点访问序列为: b
21、 a d h c e k f g。,假设找到的第一个邻接点是 c,则得到的结点访问序 列为: b c h d a e k f g,,从顶点 i 到顶点 s ,若存在路径,则从顶点 i 出发进行深度优先搜索,必能搜索到顶点 s 。,2. 遍历过程中搜索到的顶点不一定是路径上的顶点。,结论:,3. 由它出发进行的深度优先遍历已经完成的顶点不一定是路径上的顶点。,void DFSearch( int v, int s, char *PATH) / 从第v个顶点出发递归地深度优先遍历图G, / 求得一条从v到s的简单路径,并记录在PATH中 visitedv = TRUE; / 访问第 v 个顶点 A
22、ppend(PATH, getVertex(v); /第v个顶点加入路径 for(w=FirstAdjVex(v); w!=0 /从路径上删除顶点 v ,2. 求两个顶点之间的一条路径长度最短的路径,若两个顶点之间存在多条路径,则其中必有一条路径长度最短的路径。如何求得这条路径?,a,b,c,h,d,e,k,f,g,因此,求路径长度最短的路径可以基于广度优先搜索遍历进行,但需要修改链队列的结点结构及其入队列和出队列的算法。,深度优先搜索访问顶点的次序取决于图的存储结构,而广度优先搜索访问 顶点的次序是按“路径长度”渐增的次序。,将链队列的结点改为“双链”结点。即结点中包含next 和priou
23、两个指针;,2) 修改入队列的操作。插入新的队尾结点,令其priou域的指针指向刚刚出队列的结点,即当前的队头指针所指结点;,3) 修改出队列的操作。出队列时,仅移动队头指针,而不将队头结点从链表中删除。,typedef DuLinkList QueuePtr; void InitQueue(LinkQueue,例如:求下图中顶点 3 至顶点 5 的一条最短路径。,链队列的状态如下所示:,3 1 2 4 7 5,Q.front Q.rear,3,2,1,4,7,5,6,8,9,7.4 (连通网的)最小生成树,假设要在 n 个城市之间建立通讯联络网,则连通 n 个城市只需要修建 n-1条线路,如
24、何在最节省经费的前提下建立这个通讯网?,问题:,构造网的一棵最小生成树,即:在 e 条带权的边中选取 n-1 条边(不构成回路),使“权值之和”为最小。,算法二:(克鲁斯卡尔算法),该问题等价于:,算法一:(普里姆算法),取图中任意一个顶点 v 作为生成树的根,之后往生成树上添加新的顶点 w。 在添加的顶点 w 和已经在生成树上的顶点v 之间必定存在一条边,并且该边的权值在所有连通顶点 v 和 w 之间的边中取值最小。之后继续往生成树上添加顶点,直至生成树上含有 n-1 个顶点为止。,普里姆算法的基本思想:,在生成树的构造过程中,图中 n 个顶点分属两个集合:已落在生成树上的顶点集 U 和尚未
25、落在生成树上的顶点集V-U ,则应在所有连通U中顶点和V-U中顶点的边中选取权值最小的边。,一般情况下所添加的顶点应满足下列条件:,算法一:(普里姆算法),U,V-U,例如以a为顶点求下图的最小生成树:,所得生成树权值和,= 14+8+3+5+16+21 = 67,a,b,c,d,e,g,f,19,5,14,18,27,16,8,21,3,a,e,12,d,c,b,g,f,7,14,8,5,3,16,21,设置一个辅助数组,对当前VU集中的每个顶点,记录和顶点集U中顶点相连接的代价最小的边:,typedef struct VertexType adjvex;/U集中的顶点序号 VRType l
26、owcost; / 边的权值 closedgeMAX_VERTEX_NUM;,算法一:(普里姆算法),a,b,c,d,e,g,f,19,5,14,18,27,16,8,21,3,a,e,12,d,c,b,7,a,a,a,19,14,18,14,例如:,e,12,e,e,8,16,8,d,3,d,d,7,21,3,c,5,5,16,算法一:(普里姆算法),void MiniSpanTree_P(MGraph G, VertexType u) /用普里姆算法从顶点u出发构造网G的最小生成树k = LocateVex ( G, u ); for ( j=0; jG.vexnum;+j )/辅助数组初
27、始化 if (j!=k) /以邻接矩阵作为存储结构,且用一个无穷大的数表示无边直接相连。 closedgej = u, G.arcskj.adj ; closedgek.lowcost = 0; / 初始,Uu for (i=0; iG.vexnum; +i) ,继续向生成树上添加顶点; ,算法一:(普里姆算法),k = minimum(closedge); / 求出加入生成树的下一个顶点(k) printf(closedgek.adjvex, G.vexsk); / 输出生成树上一条边 closedgek.lowcost = 0; / 第k顶点并入U集 for (j=0; jG.vexnum
28、; +j) /修改其它顶点的最小边 if (G.arcskj.adj closedgej.lowcost) closedgej = G.vexsk, G.arcskj.adj ;,算法一:(普里姆算法),具体做法: 先构造一个只含 n 个顶点的子图 SG,然后从权值最小的边开始,若它的添加不使SG 中产生回路,则在 SG 上加上这条边,如此重复,直至加上 n-1条边为止。,考虑问题的出发点: 为使生成树上边的权值之和达到最小,则应使生成树中每一条边的权值尽可能地小。,克鲁斯卡尔算法的基本思想:,a,b,c,d,e,g,f,19,5,14,18,27,16,8,21,3,a,e,12,d,c,b
29、,g,f,7,14,8,5,3,16,21,例如:,7,12,18,19,算法描述:,构造非连通图 ST=( V, ); k = i = 0; / k 计选中的边数 while (kn-1) +i; 检查边集 E 中第 i 条权值最小的边 (u,v); 若(u,v)加入ST后不使ST中产生回路,则 输出边(u,v); 且 k+;,普里姆算法,克鲁斯卡尔算法,时间复杂度,O(n2),O(eloge),稠密图,稀疏图,算法名,适应范围,比较两种算法,7.5 两点之间的最短路径问题,深度优先搜索访问顶点的次序取决于图的存储结构,而广度优先搜索访问顶点的次序是按“路径长度”渐增的次序。,a,b,c,h
30、,d,e,k,f,g,因此,求路径长度最短的路径可以基于广度优先搜索遍历进行,一旦遇到目标顶点就找到了最短路。,但是,大多数情况是边上是带有权重的:,求从某个源点到其余各点的最短路径,每一对顶点之间的最短路径,1.求从源点到其余各点的最短路径的算法的基本思想:,依最短路径的长度递增的次序求得各条路径,其中,从源点到顶点v1的最短路径是所有最短路径中长度最短者。,在这条路径上,必定只含一条弧,并且这条弧的权值最小。,下一条路径长度次短的最短路径的特点:,路径长度最短的最短路径的特点:,它只可能有两种情况:或者是直接从源点到该点(只含一条弧); 或者是从源点经过顶点v1,再到达该顶点(由两条弧组成
31、)。,其余最短路径的特点:,再下一条路径长度次短的最短路径的特点:,它可能有三种情况:或者是直接从源点到该点(只含一条弧); 或者是从源点经过顶点v1,再到达该顶点(由两条弧组成);或者是从源点经过顶点v2,再到达该顶点。,它或者是直接从源点到该点(只含一条弧);或者是从源点经过已求得最短路径的顶点,再到达该顶点。,求最短路径的迪杰斯特拉算法:,一般情况下, Distk = 或者 = + 。,设置辅助数组Dist,其中每个分量 Distk 表示当前所求得的从源点到其余各顶点 k 的最短路径。,1)在所有从源点出发的弧中选取一条权值最小的弧,即为第一条最短路径。,2)修改其它各顶点的Distk值
32、。假设求得最短路径的顶点为u,若 Distu+G.arcsukDistk则将 Distk 改为 Distu+G.arcsuk。,V0和k之间存在弧,V0和k之间不存在弧,其中的最小值即为最短路径的长度。,迪杰斯特拉算法,Void ShortestPath_DIJ( MGraph G, int v0, PathMatrix ,for (i=1; iG.vexnum; +i) min=INFINITY; for (w=0; wG.vexnum; +w) if(! finalw) if (Dwmin) v=w; min=Dw; finalv =TRUE; for( w=0; wG.vexnum; +
33、w) if(! Finalw / if / for,2.求每一对顶点之间的最短路径,弗洛伊德(floyd)算法的基本思想是:,从 vi 到 vj 的所有可能存在的路径中,选出一条长度最短的路径。,依次类推,则 vi 至 vj 的最短路径应是上述这些路径中,路径长度最小者。,若存在,则存在路径vi,vj / 路径中不含其它顶点若,存在,则存在路径vi,v1,vj / 路径中所含顶点序号不大于1若vi,v2, v2,vj存在, 则存在一条路径vi, , v2, vj / 路径中所含顶点序号不大于2 ,D(-1)ij=arcsij,D(k)ij=minD(k-1)ij,D(k-1)ik+ D(k-1)kj ,N-1k0,D(-1),D(0),D(1),D(n-1),其中n为矩阵的阶数。,D(1)ij是从vi到vj的中间顶点的序号不大于1的最短路径的长度;D(k)ij是从vi到vj的中间顶点的序号不大于k的最短路径的长度;D(n-1)ij是从vi到vj的最短路径的长度;,2,1,2,1,2,1,2,1,