1、第7章 图,第 2 页,7.1 图的术语与定义,图的定义 图(Graph)图G是由两个集合V(G)和E(G)组成的,记为G=(V,E)其中:V(G)是顶点的非空有限集E(G)是边的有限集合,边是顶点的无序对或有序对。 图的分类有向图无向图,第 3 页,7.1 图的术语与定义,图的定义 有向图有向图G是由两个集合V(G)和E(G)组成的。其中:V(G)是顶点的非空有限集。E(G)是有向边(也称弧)(的有限集合,弧是顶点的有序对,记为,v,w是顶点,v为弧尾,w为弧头。,第 4 页,7.1 图的术语与定义,例如:G1 = V1 = A, B, C, D, E E1 = , , , , , , ,E
2、,A,C,B,D,第 5 页,7.1 图的术语与定义,图的定义 无向图无向图G是由两个集合V(G)和E(G)组成的。其中:V(G)是顶点的非空有限集。E(G)是边的有限集合,边是顶点的无序对,记为 (v,w) 或 (w,v),并且(v,w)=(w,v)。,第 6 页,7.1 图的术语与定义,例如:G2 = V2 = v0 ,v1,v2,v3,v4 E2 = (v0,v1), (v0,v3), (v1,v2), (v1,v4), (v2,v3), (v2,v4) ,V0,V4,V3,V1,V2,第 7 页,7.1 图的术语与定义,例如:G2 = V2 = v0,v1,v2,v3 E2 = , ,
3、 , ,图G1中:V(G1) = 1 , 2 , 3 , 4 , 5, 6 E(G1) = , , , , , , ,第 8 页,7.1 图的术语与定义,图的应用举例例1、交通图(公路、铁路)顶点:地点 边:连接地点的路例2、电路图顶点:元件 边:连接元件之间的线路例3、通讯线路图顶点:地点 边:地点间的连线例4、各种流程图如产品的生产流程图。顶点:工序 边:各道工序之间的顺序关系,第 9 页,7.1 图的术语与定义,图的基本术语 1 邻接点及关联边邻接点:边的两个顶点关联边:若边e= (v, u), 则称顶点v、u 关连边e 2 顶点的度、入度、出度 顶点V的度 = 与V相关联的边的数目在有
4、向图中: 顶点V的出度 = 以V为起点有向边数顶点V的入度 = 以V为终点有向边数顶点V的度 = V的出度+V的入度设图G 的顶点数为 n,边数为 e图的所有顶点的度数和 = 2*e(每条边对图的所有顶点的度数和“贡献”2度),e,第 10 页,7.1 图的术语与定义,路径、回路无向图 G =(V,E)中的顶点序列v1, v2, ,vk,若 (vi,vi+1)E ( i=1, 2 , k-1), v=v1, u=vk,则称该序列是从顶点v到顶点u的路径;若v=u,则称该序列为回路。有向图 D =(V,E)中的顶点序列 v1, v2, , vk,若 E (i=1,2,k-1),v=v1,u=vk
5、,则称该序列是从顶点v到顶点u的路径;若v=u,则称该序列为回路。,第 11 页,7.1 图的术语与定义,例如在图G1中,V0, V1, V2, V3 是 V0 到 V3 的路径;V0, V1, V2, V3, V0 是回路。在图G2中,V0, V2, V3 是 V0 到 V3 的路径; V0, V2, V3, V0 是回路。,无向图G1,有向图G2,第 12 页,7.1 图的术语与定义,连通图(强连通图)在无(有)向图 G= 中,若对任何两个顶点 v、u 都存在从 v 到 u 的路径,则称G是连通图(强连通图),非连通图,连通图,强连通图,非强连通图,第 13 页,7.1 图的术语与定义,子
6、图设有两个图 G=(V,E),G1=(V1,E1),若V1 V,E1 E,E1关联的顶点都在 V1 中,则称 G1 是 G 的子图; 例 (b)、(c) 是 (a) 的子图,(a),(b),(c),第 14 页,7.1 图的术语与定义,连通分图(强连通分量)无向图G的极大连通子图称为G的连通分量。极大连通子图意思是:该子图是G连通子图,将G的任何不在该子图中的顶点加入,子图不再连通。,连通分图,非连通图,第 15 页,7.1 图的术语与定义,连通分图(强连通分量)有向图D的极大强连通子图称为D的强连通分量。极大强连通子图意思是:该子图是D强连通子图,将D的任何不在该子图中的顶点加入,子图不再是
7、强连通的。,第 16 页,7.1 图的术语与定义,生成树包含无向图 G 所有顶点的极小连通子图称为G生成树。极小连通子图意思是:该子图是G的连通子图,在该子图中删除任何一条边,子图不再连通,若T是G的生成树当且仅当T满足如下条件:T是G的连通子图T包含G的所有顶点T中无回路,连通图G1,G1的生成树,第 17 页,7.2 图的存储结构,一、数组表示法(邻接矩阵表示)邻接矩阵:G的邻接矩阵是满足如下条件的n阶矩阵:,在数组表示法中,用邻接 矩阵表示顶点间的关系,第 18 页,7.2 图的存储结构,二、邻接表邻接表是图的链式存储结构1、无向图的邻接表顶点:通常按编号顺序将顶点数据存储在一维数组中;
8、关联同一顶点的边:用线性链表存储。,该结点表示边 (V2,V4),其中的4是V4在一 维数组中的位置,第 19 页,7.2 图的存储结构,结点typedef struct ArcNode int adjvex; / 邻接点域, / 存放与Vi邻接的点在表头数组中的位置struct ArcNode *next; / 链域,下一条边或弧 ArcNode; 表头结点typedef struct tnode VertexType vexdata; / 存放顶点信息ArcNode * firstarc; / 指向第一个邻接点 VNode, AdjList MAX_VERTEX_NUM ; typedef
9、 struct AdjList vertices; int vexnum, arcnum; / 顶点数和弧数int kind; / 图的种类,第 20 页,7.2 图的存储结构,无向图的邻接表的特点1)在G邻接表中,同一条边对应两个结点;2)顶点v的度:等于v对应线性链表的长度;3)判定两顶点v ,u是否邻接:要看v对应线性链表中有无对应的结点。4)在G中增减边:要在两个单链表插入、删除结点;5)设存储顶点的一维数组大小为 m(m图的顶点数n),图的边数为 e,G占用存储空间为:m+2*e。G占用存储空间与G的顶点数、边数均有关;适用于边稀疏的图。,第 21 页,7.2 图的存储结构,二、邻接
10、表2、有向图的邻接表顶点:用一维数组存储(按编号顺序)以同一顶点为起点的弧:用线性链表存储,adjvex,next,类似于无向图的邻接表,所不同的是: 以同一顶点为起点的弧:用线性链表存储,第 22 页,7.2 图的存储结构,二、邻接表3、有向图的逆邻接表顶点:用一维数组存储(按编号顺序)以同一顶点为终点的弧:用线性链表存储。,类似于有向图的邻接表,所不同的是: 以同一顶点为终点弧:用线性链表存储,第 23 页,7.2 图的存储结构,三、有向图的十字链表表示法,第 24 页,7.2 图的存储结构,三、有向图的十字链表表示法,相同弧尾,相同弧头,第 25 页,7.2 图的存储结构,四、无向图的邻
11、接多重表表示法,第 26 页,7.2 图的存储结构,四、无向图的邻接多重表表示法,第 27 页,7.3 图的遍历,连通图的深度遍历(DFS)从图中某顶点v出发: 1)访问顶点v; 2)对v的每个未被访问的邻接点wj,从wj出发,继续对图进行深度优先遍历。,深度遍历: V1,V2,V4,V5,V8,V3,V6,V7,例,深度遍历: V1,V3,V6,V7,V2,V5,V8,V4,第 28 页,7.3 图的遍历,图的深度遍历(DFS),V,w1,SG1,SG2,SG3,w2,w3,w2,W1、W2和W3 均为 V 的邻接点,SG1、SG2 和 SG3 分别为含顶点W1、W2和W3 的子图。,访问顶
12、点 V : for (W1、W2、W3 )若该邻接点W未被访问,则从它出发进行深度优先搜索遍历。,第 29 页,7.3 图的遍历,图的深度遍历(DFS),V,w1,SG1,SG2,SG3,w2,w3,w2,从图解可见:,1. 从深度优先搜索遍历连通图的过程类似于树的先根遍历;,解决的办法是:为每个顶点设立一个 “访问标志 visitedw”。,2. 如何判别V的邻接点是否被访问?,第 30 页,7.3 图的遍历,Boolean visitedMAX; / 访问标志数组 Status (*VisitFunc)(int v); / 访问函数 void DFS ( Graph G, int v) /
13、 从顶点v出发,深度优先搜索遍历连通图 Gvisitedv = TRUE; VisitFunc(v);for ( w=FirstAdjVex(G, v);w!=0; w=NextAdjVex(G,v,w) )if ( !visitedw ) DFS(G, w); / 对v的尚未访问的邻接顶点w/ 递归调用DFS / DFS,第 31 页,7.3 图的遍历,非连通图的深度优先搜索遍历首先将图中每个顶点的访问标志设为 FALSE,之后搜索图中每个顶点,如果未被访问,则以该顶点为起始点,进行深度优先搜索遍历,否则继续检查下一顶点。,第 32 页,7.3 图的遍历,void DFSTraverse (
14、 Graph G, Status (*Visit) (int v) / 对图 G 作深度优先遍历。VisitFunc = Visit; for ( v=0; vG.vexnum; +v ) visitedv = FALSE; / 访问标志数组初始化for ( v=0; vG.vexnum; +v ) if (!visitedv) DFS(G, v);/ 对尚未访问的顶点调用DFS ,第 33 页,7.3 图的遍历,例如:,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,b,g,a,c,h,k,f,e,d,b
15、,g,访问标志:,访问次序:,0 1 2 3 4 5 6 7 8,第 34 页,7.3 图的遍历,图的深度遍历(DFS)算法7.4和7.5,开始,访问V,置标志,求V邻接点,有邻接点w,求下一邻接点,W访问过,结束,N,Y,N,Y,DFS(G,w),开始,标志数组初始化,V=0,V访问过?,DFS(g,v),V=V+1,VVexNum,结束,N,Y,Y,N,第 35 页,7.3 图的遍历,图的深度遍历(DFS)递归算法,深度遍历:V1,V3 ,V7 ,V6 ,V2 ,V4 ,V8 ,V5,第 36 页,7.3 图的遍历,图的广度遍历(BFS)从图的某一顶点V0出发,访问此顶点后,依次访问V0的
16、各个未曾访问过的邻接点;然后分别从这些邻接点出发,广度优先遍历图,直至图中所有已被访问的顶点的邻接点都被访问到;若此时图中尚有顶点未被访问,则另选图中一个未被访问的顶点作起点,重复上述过程,直至图中所有顶点都被访问为止。,广度遍历:V1 V2 V3 V4 V5 V6 V7 V8,第 37 页,7.3 图的遍历,图的广度遍历(BFS)从图中的某个顶点V0出发,并在访问此顶点之后依次访问V0的所有未被访问过的邻接点,之后按这些顶点被访问的先后次序依次访问它们的邻接点,直至图中所有和V0有路径相通的顶点都被访问到。,若此时图中尚有顶点未被访问,则另选图中一个未曾被访问的顶点作起始点,重复上述过程,直
17、至图中所有顶点都被访问到为止。,第 38 页,7.3 图的遍历,图的广度遍历(BFS)从图中某顶点v出发:1) 访问顶点v2) 访问v的所有未被访问的邻接点w1,w2,wk3) 依次从这些邻接点出发,访问它们的所有未被访问的邻接点,直到图中所有访问过的顶点的邻接点都被访问到。,V1,V2,V3,V4,V8,V5,V6,V7,V1,V3,V2,V6,V7,V4,V5,V8,第 39 页,7.3 图的遍历,void BFSTraverse ( Graph G,Status (*Visit) (int v) ) for ( v=0; vG.vexnum; +v )visitedv = FALSE;
18、/ 初始化访问标志InitQueue(Q); / 置空的辅助队列Qfor ( v=0; vG.vexnum; +v )if ( !visitedv) / v 尚未访问 / BFSTraverse,第 40 页,7.3 图的遍历,visitedv = TRUE; Visit(v); / 访问v EnQueue(Q, v); / v入队列 while (!QueueEmpty(Q) DeQueue(Q, u); / 队头元素出队并置为ufor ( w=FirstAdjVex(G, u); w!=0; w=NextAdjVex(G,u,w) )if ( ! visitedw ) visitedw=T
19、RUE; Visit(w);EnQueue(Q, w); / 访问的顶点w入队列 / if / while,第 41 页,7.3 图的遍历,图的广度遍历(BFS),第 42 页,7.3 图的遍历,图的广度遍历(BFS),第 43 页,7.3 图的遍历,图的广度遍历(BFS),第 44 页,7.4 图的最小生成树,问题提出要在n个城市间建立通信联络网,顶点表示城市权城市间建立通信线路所需花费代价希望找到一棵生成树,它的每条边上的权值之和(即建立该通信网所需花费的总代价)最小最小代价生成树 问题分析n个城市间,最多可设置 n(n-1)/2 条线路。n个城市间建立通信网,只需 n-1 条线路。问题转
20、化为:如何在可能的线路中选择 n-1 条,能把所有城市(顶点)均连起来,且总耗费(各边权值之和)最小。,第 45 页,7.4 图的最小生成树,普里姆算法的基本思想取图中任意一个顶点 v 作为生成树的根,之后往生成树上添加新的顶点 w。在添加的顶点 w 和已经在生成树上的顶点 v 之间必定存在一条边,并且该边的权值在所有连通顶点 v 和 w 之间的边中取值最小。之后继续往生成树上添加顶点,直至生成树上含有 n-1 个顶点为止。,第 46 页,7.4 图的最小生成树,普里姆算法(Prim)设 G=(V,GE)为一个具有 n 个顶点的连通网络,T=(U,TE)为构造的生成树。(1)初始时,U = u
21、0,TE = ;(2)在所有 uU 、vV-U 的边(u,v)中选择一条权值最小的边,不妨设为(u,v);(3)(u,v) 加入TE,同时将 v 加入U;(4)重复(2)(3),直到 U=V 为止;,U,V-U,v,u,U扩大,V-U缩小,v,u,第 47 页,7.4 图的最小生成树,普里姆算法(Prim)教材P175,U= V1 ,U= V1,V3 ,U= V1,V3,V6,U= V1,V3,V6,V4 ,U= V1,V3,V6,V4,V2 ,U= V1,V3,V6,V4,V2,V5 ,第 48 页,7.4 图的最小生成树,普里姆算法的基本思想设置一个辅助数组,对当前 V-U 集中的每个顶点
22、,记录和顶点集U中顶点相连接的代价最小的边:,struct VertexType adjvex; / U集中的顶点序号VRType lowcost; / 边的权值 closedge MAX_VERTEX_NUM ;,第 49 页,7.4 图的最小生成树,普里姆算法的基本思想,有关数据的存储结构无向连通网络: MGraph G选择权值最小的边:设置一个一维数组closedge ,对 vV-U closedgev.lowcost = min costu,v| uU /保存连接v到U中各顶点权最小边的权值 closedgev.adjvex = u /用来记录该边在U中的顶点,第 50 页,7.4 图
23、的最小生成树,普里姆算法的基本思想,v1 v1 v1 0 6 1 5,v3 v1 v3 v3 0 5 0 5 6 4,U= v1 ,U= v1,v3 ,U,U,第 51 页,7.4 图的最小生成树,普里姆算法的基本思想,i,closedge,0 1 2 3 4 5,U,第 52 页,7.4 图的最小生成树,void MiniSpanTree_P ( MGraph G, VertexType u ) /用普里姆算法从顶点u出发构造网G的最小生成树k = LocateVex ( G, u ); for ( j=0; jG.vexnum; +j ) / 辅助数组初始化if ( j!=k )close
24、dge j = u, G.arcs k j .adj ; / 从u点开始初始化 adjvex,lowcost closedgek.lowcost = 0; / 初始,Uufor (i=0; iG.vexnum; +i) 继续向生成树上添加顶点; /MiniSpanTree,第 53 页,7.4 图的最小生成树,k = minimum(closedge); / 求出加入生成树的下一个顶点(k)printf ( closedgek.adjvex, G.vexsk ); / 输出生成树上一条边closedgek.lowcost = 0; / 第k顶点并入U集for ( j=0; jG.vexnum;
25、 +j )/ 修改其它顶点的最小边if ( G.arcskj.adj closedgej.lowcost )closedge j = G.vexsk, G.arcskj.adj ;,第 54 页,7.4 图的最小生成树,克鲁斯卡尔(Kruskal)算法考虑问题的出发点:为使生成树上边的权值之和达到最小,则应使生成树中每一条边的权值尽可能地小。,具体做法:先构造一个只含 n 个顶点的子图SG,然后从权值最小的边开始,若它的添加不使SG中产生回路,则在SG 上加上这条边,如此重复,直至加上n-1条边为止。,第 55 页,7.4 图的最小生成树,克鲁斯卡尔(Kruskal)算法设连通网 N = (
26、V, E )。令最小生成树初始状态为只有 n 个顶点而无边的非连通图 T = (V, ),每个顶点自成一个连通分量。在E中选取代价最小的边,若该边依附的顶点落在 T 中不同的连通分量上,则将此边加入到 T中;否则,舍去此边,选取下一条代价最小的边。依此类推,直至T中所有顶点都在同一连通分量上为止。,第 56 页,7.4 图的最小生成树,克鲁斯卡尔(Kruskal)算法,第 57 页,7.4 图的最小生成树,构造非连通图 ST=( V, ); k = i = 0; / k 计选中的边数 while (kn-1) +i;检查边集 E 中第 i 条权值最小的边(u,v);若(u,v)加入ST后不使S
27、T中产生回路,则 输出边(u,v); 且 k+; ,第 58 页,7.4 图的最小生成树,克鲁斯卡尔(Kruskal)算法,1,1,1,1,1,4,2,1,1,1,2,2,2,2,2,采用边集数组的形式保存图:,第 59 页,7.4 图的最小生成树,两种算法比较,普里姆算法,克鲁斯卡尔算法,时间复杂度,O(n2),O(eloge),稠密图,稀疏图,算法名,适应范围,第 60 页,思考题答案,用邻接矩阵表示图时,矩阵元素的个数与顶点个数是否相关?与边的条数是否相关? 【解答】用邻接矩阵表示图,矩阵元素的个数是顶点个数的平方,与边的条数无关。矩阵中非零元素的个数与边的条数有关。,第 61 页,思考
28、题答案,有n个顶点的无向连通图至少有多少条边?有n个顶点的有向强连通图至少有多少条边? 【解答】n个顶点的无向连通图至少有n-1条边,n个顶点的有向强连通图至少有n条边。 对于有n个顶点的无向图,采用邻接矩阵表示,如何判断以下问题:图中有多少条边?任意两个顶点i和j之间是否有边相连?任意一个顶点的度是多少? 【解答】用邻接矩阵表示无向图时,因为是对称矩阵,对矩阵的上三角部分或下三角部分检测一遍,统计其中的非零元素个数,就是图中的边数。如果邻接矩阵中Aij 不为零,说明 顶点i 与 顶点j 之间有边相连。此外统计矩阵 第i行 或 第i列 的非零元素个数,就可得到顶点i的度数。,第 62 页,思考
29、题,1、在有向图的邻接表存储结构中,顶点v在链表中出现的次数是A)顶点的v的度 B)顶点v的出度C)顶点v的入度 D)依附于顶点v的边数 答案:C 2、用邻接矩阵表示图时,若图中有100个顶点,100条边,则形成的矩阵有多少元素?有多少非零元素? 答案:邻接矩阵中的元素有1002 = 10000个。它有100个非零元素(对于有向图)或200个非零元素(对于无向图)。,第 63 页,思考题,求稀疏矩阵的和。若稀疏矩阵采用带行指针向量的链式存储形式。若结点定义为:struct triplenode / 定义三元组结点 int col; / 列值elemtype val; / 非0元素值struct node * next; / 指针域带行指针向量的链式存储结构定义为:#define MAXROW 整型常量(行最大长度)struct L_SMatrix int m, n, t / 矩阵行数,列数,非0元素数struct triplenode * HeadsMAXROW+1 / 0不用,第 64 页,思考题,求稀疏矩阵的和。例如:有 65 的稀疏矩阵如下。,col val,2 0 0 3 0 0 0 0 0 0 -1 0 0 0 0 0 0 0 8 0 0 0 0 0 0 1 0 4 0 0,