1k likes | 1.18k Views
第7章 图. 7.1 图的定义和术语 7.2 图的存储结构 7.3 图的遍历 7.4 最小生成树 7.5 有向无环图及应用 7.6 最短路径 习题. 7.1 图的定义和术语. 一、图的定义 图 (Graph) :是一种多对多的结构关系,每个元素可以有零个或多个直接前趋;零个或多个直接后继; 图 G 由两个集合构成,记作: G=(V,E) 其中: V: 顶点的非空有限集合, E: 边的有限集合,边是顶点的无序对或有序对集合。 网 :带权的图。 无向图 :在图 G 中,若所有边是无向边。
E N D
第7章 图 7.1 图的定义和术语 7.2 图的存储结构 7.3 图的遍历 7.4 最小生成树 7.5 有向无环图及应用 7.6 最短路径 习题
7.1 图的定义和术语 一、图的定义 • 图(Graph):是一种多对多的结构关系,每个元素可以有零个或多个直接前趋;零个或多个直接后继; • 图G由两个集合构成,记作: G=(V,E) 其中: • V:顶点的非空有限集合, • E:边的有限集合,边是顶点的无序对或有序对集合。 • 网:带权的图。 • 无向图:在图G中,若所有边是无向边。 • 有向图:在图G中,若所有边是有向边。 • 混合图:在图G中,既有无向边也有有向边。
V4 V2 V3 V1 V5 • 例1: G1=(V1,E1) V1={v1,v2,v3,v4 ,v5 } E1={(v1,v2),(v1,v4),(v2,v3),(v2,v5),(v3,v4),(v3,v5)} 顶点 边 G1图示
V1 V2 V3 V4 • 例2: G2=(V2,E2) V2={v1,v2,v3,v4 } E2={<v1,v2>, <v1,v3>, <v3,v4> , <v4,v1>} 顶点 弧 弧尾(初始点) 弧头(终端点) G2图示
抽象数据类型图的定义 • ADT Graph { • 数据对象V: V是具有相同特性的数据元素的集合,称为顶点集。 • 数据关系R: R={VR} VR= {<v,w>| v,w∈V且P(v,w), <v,w>表示从v到w的弧,谓词P(v,w)定义了弧<v,w>的意义或信息 } • 基本操作P: …… }ADT Graph
基本操作 • 结构的建立和销毁: • CreateGraph(&G,V,VR); • 按V和VR的定义构造图G。 • DestroyGraph(&G); • 销毁图G。 • 对顶点的访问操作: • LocateVex(G, u); • 若G中存在顶点u,则返回该顶点在图中位置;否则返回其它信息。 • GetVex(G, v); • 返回v的值。 • PutVex(&G, v, value); • 对v赋值value。
对邻接点的操作: • FirstAdjVex(G, v); • 返回v的第一个邻接点。若该顶点在G中没有邻接点,则返回“空”。 • NextAdjVex(G, v, w); • 返回v的(相对于w的)下一个邻接点。若w是v的最后一个邻接点,则返回“空” • 插入或删除顶点 • InsertVex(&G, v); • 在图G中增添新顶点v。 • DeleteVex(&G, v); • 删除G中顶点v及其相关的弧。
插入和删除弧 • InsertArc(&G, v, w); • 在G中增添弧<v,w>,若G是无向的,则还增添对称弧<w,v>。 • DeleteArc(&G, v, w); • 在G中删除弧<v,w>,若G是无向的,则还删除对称弧<w,v>。 • 遍历 • DFSTraverse(G, v, Visit()); • 从顶点v起深度优先遍历图G,并对每个顶点调用函数Visit一次且仅一次。 • BFSTraverse(G, v, Visit()); • 从顶点v起广度优先遍历图G,并对每个顶点调用函数Visit一次且仅一次。
V4 V2 V3 V1 V5 二、图的应用举例 • 例1. 交通图(公路、铁路) • 顶点:地点 • 边:连接地点的公路 • 交通图中的有单行道双行道,分别用有向边、无向边表示; • 例2. 电路图 • 顶点:元件 • 边:连接元件之间的线路
例3. 通讯线路图 • 顶点:地点 • 边:地点间的连线 • 例4. 各种流程图(如:产品的生产流程图) • 顶点:工序 • 边:各道工序之间的顺序关系
V4 V2 V3 V1 V5 • 三、图的基本术语 • 完全图:图G任意两个顶点都有一条边相连接 • 若 n 个顶点的无向图有 n(n-1)/2 条边, 称为无向完全图 • 若 n 个顶点的有向图有n(n-1)条边, 称为有向完全图 • 稀疏图:边较少的图。通常e<<nlogn • 稠密图:边很多的图。 e1
V4 V2 V1 V3 V2 V3 V4 V1 V5 • 邻接(adjacent)点:边的两个顶点 • 关联(incident)边:若边e= (v, u), 则称顶点v、u 关联边e • 顶点的度:和某顶点相连的邻接点数 • 顶点的入度:指向某顶点的弧的数目 • 顶点的出度:从某顶点出发的弧的数目 • 设图G的顶点数为n,边数为e • 图的所有顶点的度数和 = 2*e • 每条边对图的所有顶点的度数和“贡献”2度
V4 V2 V3 V1 V2 V3 V4 V1 V5 • 路径:两个顶点之间的顶点序列。 • 该序列的每个顶点与其前驱是邻接点,每个顶点与其后继也是邻接点 • 回路(环):第一顶点和最后顶点相同的路径。 • 图1:V1,V2,V3,V4 是V1到V4的路径;V1,V2,V3,V4,V1是回路; • 图2:V1,V3,V4 是V1到V4的路径; V1,V3,V4,V1是回路。 • 简单路径: 顶点不重复的路径。 图1 图2
V4 V2 V2 V5 V6 V1 V3 V4 V1 V5 V3 • 连通: 两个顶点之间有路径 • 连通图: 任意两个顶点之间有路径(无向图) • 强连通图:任意两个顶点之间有双向路径(有向图) 连通图 非连通图
V4 V4 V2 V2 V2 V3 V3 V3 V1 V1 V1 V5 V5 V5 • 子图:设有两个图G=(V,E)、G1=(V1,E1),若V1 V,E1 E,则称 G1是G的子图; • 例:图2、图3 是 图1 的子图 图2 图3 图1
V2 V5 V6 V1 V4 连通分图 V3 • 连通分图: 无向图中的极大连通子图。 • 强连通分量:有向图中的极大强连通子图。 • 极大(强)连通子图:该子图是图G的(强)连通子图,将G的任何不在该子图中的顶点加入,子图不再(强)连通。
V4 V4 V2 V2 V3 V3 V1 V1 V5 V5 • 生成树:包含无向图G所有顶点的的极小连通子图。 • 极小连通子图:该子图是G的连通子图,在该子图中删除任何一条边,子图不再连通 • 若T是G的生成树当且仅当T满足如下条件: • T是G的连通子图 • T包含G的所有顶点 • T中无回路
7.2 图的存储结构 • 图的存储结构至少要保存两类信息: • 1)顶点的数据 • 2)顶点间的关系 • 顶点的编号 • 为了使图的存储结构与图一一对应,在讨论图的存储结构时,首先要给图的所有顶点编号。 • 设 G=<V, E>是图, V={v1,v2,v3, …vn },设顶点的的下标为它的编号。 • 本课程介绍两类图的存储结构 • 数组表示法 • 邻接表(邻接表,逆邻接表)
V4 V2 V3 V1 V2 V3 V4 • 0 1 0 1 0 • 0 1 0 1 • 0 1 0 1 1 • 0 1 0 0 • 0 1 1 0 0 • 0 1 1 0 • 0 0 0 0 • 0 0 0 1 • 0 0 0 V1 V5 一、数组表示法 • 用邻接矩阵表示顶点间的关系。 • 邻接矩阵:G的邻接矩阵是满足如下条件的n阶矩阵: 1 若(vi,vi+1)E 或 <vi,vi+1>E 0 否则 A[i][j]=
Wij <vi, vj> 或(vi, vj)∈VR A [ i ][ j ]= ∞ 无边(弧) 5 v1 v2 4 8 3 N 7 9 v3 v6 6 1 5 v4 v5 5 • 网(即有权图)的邻接矩阵 ∞5∞ 7 ∞∞ ∞∞4∞∞∞ 8∞∞∞ ∞9 ∞∞5 ∞∞6 ∞∞∞ 5 ∞∞ 3∞∞ ∞ 1∞
0 1 0 1 0 1 0 1 0 1 0 1 2 3 4 m m+1 m+2 m+3 m+4 V1 V2 V3 V4 V5 0 1 2 3 4 5 m-1 顶点的存储:用一维数组存储(按编号顺序); 顶点间关系:用二维数组存储图的邻接矩阵。 存储邻接矩阵的二维数组 存储顶点的一维数组
数组表示法类型定义 #define MAX_VERTEX_NUM m //最大顶点个数 typedef enum {DG,DN,UDG,UDN} GraphKind; //{有向图,有向网,无向图,无向网} typedef struct ArcCell { VRType adj; //VRType 是顶点关系类型。 //无权图:用1或0表示相邻否;对带权图:则为权值类型 }ArcCell, AdjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; typedef struct { VertexType vexs[MAX_VERTEX_NUM]; //存储顶点 AdjMatrix arcs; //存储邻接矩阵 int vexnum, arcnum; //图的当前顶点数和弧数 GraphKind kind; //图的种类标志 }Mgraph;
顶点数组 V1 0 n e UDG G.vexs G.arcs G.vexnumG.arcnu G.kind 存储邻接矩阵的二维数组 • 设G是Mgraph 类型的变量,用于存储无向图,该图有n个顶点,e条边。 • G的图示如下:
无向图数组表示法特点: • 1)无向图邻接矩阵是对称矩阵,同一条边表示了两次; • 2)顶点v的度:二维数组对应行(或列)中1的个数; • 3)两顶点是否为邻接点:只需判二维数组对应分量是否为1; • 4)增加、删除边:只需对二维数组对应分量赋值1或清0; • 5)设存储顶点的一维数组大小为n, G占用存储空间:n+n2。G占用存储空间只与它的顶点数有关,与边数无关。 • 对有向图的数组表示法可做类似的讨论。
用邻接矩阵生成无向网的算法 Status CreateUDN(Mgraph &G) { //无向网的构造,用邻接矩阵表示 scanf(&G.vexnum, &G.arcnum, &IncInfo); for(i=0;i<G.vexnum,;++i) scanf(&G.vexs[i] ); //构造顶点向量 for(i=0; i<G.vexnum; ++i) //初始化邻接矩阵 for(j=0;j<G.vexnum;++j) G.arcs[i][j]={INFINITY, NULL}; for(k=0;k<G.arcnum;++k){ //构造邻接矩阵 scanf(&v1, &v2, &w); //输入弧的两顶点以及对应权值 i=LocateVex(G,v1); j=LocateVex(G,v2); //找到顶点在矩阵中的位置 G.arcs[i][j].adj=w; //输入对应权值 if(IncInfo) Input(*G.arcs[i][j].info); //如果弧有信息则填入 G.arcs[i][j] = G.arcs [j] [i]; //无向网是对称矩阵 } return OK; } //CreateUDN 对于n个顶点e条弧的网, 时间复杂度= O(n2+n+e*n)
0 1 2 3 4 m-1 V1 V2 V3 V4 V5 4 0 V4 V2 V3 1 3 0 1 3 2 V1 1 4 2 V5 二、邻接表 1. 无向图的邻接表 • 顶点:按编号顺序将顶点数据存储在一维数组中; • 关联同一顶点的边:用线性链表存储。 • 例 2
图的邻接表类型定义 #define MAX_VERTEX_NUM 20 typedef struct ArcNode{ //弧的类型定义 int adjvex; //弧指向的顶点的位置 struct ArcNode *nextarc; //指向下一条弧的指针 InfoType *info; }ArcNode; typedef struct Vnode { //顶点的类型定义 VertexType data; //顶点信息 ArcNode * firstarc; //指向关联该顶点的弧链表 }Vnode, AjList[MAX_VERTEX_NUM]; typedef struct { //图的类型定义 AdjList vertices; int vexnum, arcnum; //图的顶点数和弧数 int kind; //图的种类标志 }ALGraph;
0 1 2 4 m-1 1 2 V4 V2 V3 G.vertices G.vexnum G.arcnu G.kind V1 V2 V3 V4 V5 3 1 4 4 3 0 V1 2 0 1 V5 5 6 UDG • 设G是ALGraph 类型的变量,用于存储无向图G1,该图有n个顶点,e条边 • G的图示如下: data firstarc adjvex nextarc 2 无向图G1
无向图的邻接表的特点 • 1)在G邻接表中,同一条边对应两个结点; • 2)顶点v的度:等于v对应线性链表的长度; • 3)两顶点是否邻接:要看v对应线性链表中有无对应的结点u; • 4)增减边:要在两个单链表插入、删除结点; • 5)设存储顶点的一维数组大小为n, 图的边数为e,G占用存储空间为:n+2*e。G占用存储空间与G的顶点数、边数均有关。
D.vertices D.vexnum D.arcnu D.kind V1 V2 V3 V4 V1 V2 V3 V4 0 1 2 4 4 DG 3 2. 有向图的邻接表和逆邻接表 • 1)有向图的邻接表 • 顶点:用一维数组存储(按编号顺序) • 以同一顶点为起点的弧:用线性链表存储 顶点表 出边表 0 1 2 3 m-1
D.vertices D.vexnum D.arcnu D.kind V1 V2 V3 V4 V1 V2 V3 V4 3 0 0 2 4 4 DG • 2)有向图的逆邻接表 • 顶点:用一维数组存储(按编号顺序) • 以同一顶点为终点的弧:用线性链表存储表 顶点表 入边表 0 1 2 3 m-1
D.vertices D.vexnum D.arcnu D.kind A B C D 4 4 DG • 网络 (带权图) 的邻接表 1 5 1 9 3 2 2 8 顶点表 出边表 3 6 0 1 2 3 m-1 6 A D 9 5 2 B C 8
三、十字链表(自学) • 它是有向图的另一种链式结构。 • 思路:邻接表、逆邻接表的结合。 • 1、每条弧对应一个结点(称为弧结点) • 2、每个顶点也对应一个结点(称为顶点结点)
弧结点 tailvex: 弧尾顶点位置 headvex: 弧头顶点位置 hlink: 弧头相同的下一弧位置 tlink: 弧尾相同的下一弧位置 info: 弧信息 顶点结点 data : 存储顶点信息。 Firstin : 以顶点为弧头的第一条弧结点。 Firstout: 以顶点为弧尾的第一条弧结点。
十字链表存储结构: typedef struct ArcBox { //弧结点结构 int tailvex , headvex ; struct ArcBox * hlink , *tlink; InfoType *info; } ArcBox; Typedef struct VexNode{ //顶点结构 VertexType data; ArcBox * firstin,*firstout; } VexNode; #define MAX_VERTEX_NUM 20 Typedef struct { VexNode xlist[MAX_VERTEX_NUM ]; //表头向量 int vexnum, arcnum; } OLGraph;
例:画出有向图的十字链表。 v2 v1 v3 v4 0 v1 0 1 0 2 ^ 1 v2 3 2 2 3 ^ ^ ^ ^ 2 v3 2 0 3 v4 3 0 ^ 3 1 ^ ^ 数组下标 tlink tailvex hlink headvex ^ firstin firstout 十字链表优点:容易操作,如求顶点的入度、出度等。 空间复杂度与邻接表相同;建立的时间复杂度与邻接表相同。
四、邻接多重表(自学) • 无向图的另一种存储结构 • 需要对边操作时,无向图常采用此种结构存储。 • 1.每条边只对应一个结点(边结点) • 比较:邻接表中同一条边用两个结点表示 • 2.每个顶点也对应一个结点(顶点结点)
边结点 mark:标志域,如处理过或搜索过。 ivex, jvex : 顶点域,边依附的两个顶点位置。 ilink: 指向下一条依附顶点 i 的边结点位置。 jlink; 指向下一条依附顶点 j 的边结点位置。 info: 边信息,如权值等。 顶点结点 data : 存储顶点信息。 Firstedge : 依附顶点的第一条边结点。
无向图的邻接多重表存储表示 typedef struct Ebox { VisitIf mark; //访问标记 int ivex, jvex; //该边依附的两个顶点的位置 struct EBox *ilink, *jlink; InfoType *info; //该边信息指针 } Ebox; typedef struct VexBox { VertexType data; EBox *firstedge; // 指向第一条依附该顶点的边 } VexBox; typedef struct { // 邻接多重表 VexBox adjmulist[MAX_VERTEX_NUM]; int vexnum, edgenum; } AMLGraph;
例:画出无向图的邻接多重表 v2 v1 v3 v4 v4 v5 0 1 0 ^… 3 0 v1 1 2 1 ^ 4 1 v2 2 v3 2 3 2 ^ 4 3 v4 3 ^ 4 ^ 4 v5 数组下标不属结点分量 ivex jvex jlink ilink 邻接多重表优点: 容易操作,如求顶点的度等。 空间复杂度与邻接表相同; 建立的时间复杂度与邻接表相同。 firstedge
7.3 图的遍历 • 图的遍历:从图的某顶点出发,访问图中所有顶点,并且每个顶点仅访问一次。 • 图中可能有回路,遍历可能沿回路又回到已遍历过的结点。为避免同一顶点被多次访问,必须为每个被访问的顶点作一标志。 • 为此引入一辅助数组, 记录每个顶点是否被访问过。 • 有两种遍历方法: • 深度优先遍历 ( DFS, Depth First Search ) • 广度优先遍历 ( BFS , Breadth First Search )
0 1 2 3 4 5 6 7 visited 1 1 0 1 1 0 0 1 V3 V5 V8 V2 V1 V6 V4 V7 7.3.1 深度优先遍历(类似于树的先根遍历) 从图中某顶点v出发: • 1)访问顶点v; • 2)从v的未被访问的邻接点出发,继续对图进行深度优先遍历; • 例:图G中,以V1为起点的深度优先序列: • V1,V2,V4,V8,V5,V3,V6,V7 注:为简单起见,只讨论非空连通图的遍历
visited 0 0 0 0 0 0 1 2 3 4 m-1 Boolean visited[MAX_VERTEX_NUM] //访问标志数组, visited[v]=TRUE表示顶点v已被访问 深度优先遍历算法 void DFS(Graph G, int v) { // 从第v个顶点出发,递归地深度优先遍历连通图G。 // v是顶点在一维数组中的位置,假设G是非空图 visited[v] =TRUE; Visit(v); //访问第v个顶点 for (w= FirstAdjVex(G, v); w>=0; w=NextAdjVex(G, v, w)) if (!visited[w]) DFS(G, w); //对v的尚未访问的邻接顶点w递归调用DFS }
DFS 算法效率分析 设图中有 n 个顶点,e 条边 • 遍历图的过程实质上是对每个顶点查其找邻接点 • 邻接矩阵:查找每个顶点的邻接点所需的时间为O(n2)。 • 邻接表:查找邻接点需扫描 e 个结点,加上访问 n个头结点的时间,因此遍历图的时间复杂度为O(n+e)。 • 结论: • 稠密图适于在邻接矩阵上进行深度遍历; • 稀疏图适于在邻接表上进行深度遍历。
visited 1 0 0 0 0 0 0 0 V3 V5 V8 V2 V1 V6 V4 V7 7.3.2 广度优先遍历(类似于树的按层遍历) 从图中某顶点v出发: • 1)访问顶点v ; • 2)访问v的所有未被访问的邻接点w1 ,w2 ,…wk ; • 3)依次从这些邻接点出发,访问它们的所有未被访问的邻接点; 依此类推,直到图中所有访问过的顶点的邻接点都被访问; • 例:图G中,以V1起点的的广度优先序列: • V1,V2,V3,V4,V5,V6,V7,V8 0 1 2 3 4 5 6 7
为实现 3) • 需要保存在步骤2)中访问的顶点; • 访问这些顶点邻接点的顺序为: • 先保存的顶点,其邻接点先被访问。 需设置一队列Q,保存已访问的顶点,并控制遍历顶点的顺序。
void BFSTraverse(Graph G,int v,Status (* Visit)(int v)) { //从v出发,广度优先非递归遍历连通图G。 //使用辅助队列Q和访问标志数组visited。 for (u=0; u< G.vexnum; ++u) visited[u]=FALSE; InitQueue(Q); //建空的辅助队列Q visited[v]=TRUE; Visit(v);EnQueue(Q,v) //访问v,v入队 while(!QueueEmpty(Q)){ DeQueue(Q,u); //队头元素出队,并赋值给u //访问u所有未被访问的邻接点 for(w=FirstAdjVex(G,u); w>=0; w=NextAdjVex(G,u,w)) if(!visited[w]){ // 若w尚未访问 visited[w]=TRUE; Visit(w);EnQueue(Q,w); }//if }//while }//BFSTraverse
BFS 算法效率分析: • 设图中有 n 个顶点,e 条边 • 邻接矩阵:对于每一个被访问到的顶点,都要循环检测矩阵中的整整一行(n 个元素),总的时间代价为O(n2)。 • 邻接表:总时间代价为d0 + d1 + … + dn-1 = O(e),其中的di 是顶点i 的度。 • DFS与BFS之比较: • 空间复杂度:相同,O(n)(借用了堆栈或队列); • 时间复杂度:只与存储结构(邻接矩阵或邻接表)有关,而与搜索路径无关。
无向图的连通性 • 对于连通图,从图中任一顶点出发, 利用深度优先搜索算法或广度优先搜索算法,便可访问到到图中的所有顶点 • 深度优先搜索得到深度优先生成树 • 广度优先搜索得到广度优先生成树 • 对于非连通图, 则需从每一个连通分量中的任一个顶点出发进行遍历, 可求得无向图的所有连通分量 • 所有连通分量的生成树组成了非连通图的生成森林。