770 likes | 902 Views
第七章 图. 第七章 图 7.1 图的定义和术语 7.2 图的存储结构 7. 3 图的遍历 7.4 图的连通性问题 7.5 有向无环图及应用 7.6 最短路径. 第七 章 图. 本章介绍另一种非线性数据结构 —— 图 图:是一种多对多的结构关系,每个元素可以有零个或多个直接前趋;零个或多个直接后继;. 第七 章 图. 学习要点 1.熟悉图的各种存储结构及其构造算法,了解实际问题的求解效率与采用何种存储结构和算法有密切联系;
E N D
第七章 图 7.1 图的定义和术语 7.2 图的存储结构 7.3 图的遍历 7.4 图的连通性问题 7.5 有向无环图及应用 7.6 最短路径
第七 章 图 本章介绍另一种非线性数据结构 —— 图 图:是一种多对多的结构关系,每个元素可以有零个或多个直接前趋;零个或多个直接后继;
第七 章 图 学习要点 1.熟悉图的各种存储结构及其构造算法,了解实际问题的求解效率与采用何种存储结构和算法有密切联系; 2.熟练掌握图的两种遍历:深度优先遍历和广度优先遍历的算法。在学习中应注意图的遍历算法与树的遍历算法之间的类似和差异。树的先根遍历是一种深度优先搜索策略,树的层次遍历是一种广度优先搜索策略 3.理解课件中讨论的各种图的算法;
7.1 图的定义和术语 V4 V2 V3 V1 V5 一 图的概念 图G由两个集合构成,记作G=<V,E> 其中V是顶点的非空有限集合,E是边的有限集合,其中边是顶点的无序对或有序对集合。 例 G1=<V1,E1> V1={v1,v2,v3,v4 ,v5 } E1={(v1,v2),(v1,v3),(v2,v3),(v2,v5),(v3,v4),(v3,v5)} 无序对(vi,vj): 用表示顶点vi、vj的线段 ,称为无向边; G1图示
7.1 图的定义和术语 V1 V2 V3 V4 例 G2=<V2,E2> V2={v1,v2,v3,v4 } E2={<v1,v2>, <v1,v3>, <v3,v4> , <v4,v1>} V1称为弧尾(初始点) 有序对<vi,vj> : 用以表示以vi起点、以vj 为终点的有向线段, 称为有向边或弧; G2图示 V3称为弧头(终端点) 无向图:在图G中,若所有边是无向边,则称G为无向图; 有向图:在图G中,若所有边是有向边,则称G为有向图; 混和图:在图G中,即有无向边也有有向边,则称G为混合图;
7.1 图的定义和术语 V4 V2 V3 V1 V5 • 图的应用举例 • 例1 交通图(公路、铁路) • 顶点:地点 • 边:连接地点的公路 • 交通图中的有单行道双行道,分别用有向边、无向边表示; • 例2 电路图 • 顶点:元件 • 边:连接元件之间的线路 • 例3 通讯线路图 • 顶点:地点 • 边:地点间的连线 • 例4 各种流程图 • 如产品的生产流程图 • 顶点:工序 • 边:各道工序之间的顺序关系
7.1 图的定义和术语 三 图的基本操作 1 CreateGraph(&G, V, VR); 初始条件:V是图的顶点集,VR是图中弧的集合 操作结果:按V和VR的定义构造图G 2 DestroyGraph(&G); 初始条件:图G存在 操作结果:销毁图G 3 LocateVex(G,u); 初始条件:图G存在,u和G中顶点有相同特征 操作结果:若G中存在顶点u,则返回该顶点在图中位置;否则返回其它信息。 4 GetVex(G, v); 初始条件:图G存在,v是G中某个顶点 操作结果:返回v的值 5 PutVex(&G, v, value); 初始条件:图G存在,v是G中某个顶点 操作结果:对v赋值value
7.1 图的定义和术语 6 FirstAdjVex(G, v); 初始条件:图G存在,v是G中某个顶点 操作结果:返回v的第一个邻接顶点。若顶点在G中没有邻接顶点,则返回“空”。 7 NextAdjVex(G, v, w); 初始条件:图G存在,v是G中某个顶点,w是v的邻接顶点。 操作结果:返回v的(相对于w的)下一个邻接顶点。若w是v的最后一个邻接点,则返回“空”。 8 InsertVex(&G, v); 初始条件:图G存在,v和图中顶点有相同特征。 操作结果:在图G中增添新顶点v 9 DeleteVex(&G, v); 初始条件:图G存在,v和图中顶点有相同特征 操作结果:删除G中顶点v及相关的弧
7.1 图的定义和术语 10 InsertArc(&G, v, w); 初始条件:图G存在,v和w是G中两个顶点。 操作结果:在G中增添弧<v,w>,若G是无向的,则还增添对称弧<w,v> 11 DeleteArc(&G, v, w); 初始条件:图G存在,v和w是G中两个顶点。 操作结果:在G中删除弧<v,w>,若G是无向的,则还删除对称弧<w,v> 12 DFSTraverse(G, v, Visit( )); 初始条件:图G存在,v是G中某个顶点,Visit是顶点的应用函数。 操作结果:从顶点v起深度优先遍历图G,对每个顶点调用函数Visit一次且仅一次。一旦visit( )失败,则操作失败 13 BFSTraverse(G, v, Visit( )); 初始条件:图G存在,v是G中某个顶点,Visit是顶点的应用函数。 操作结果:从顶点v起广度优先遍历图G,对每个顶点调用函数Visit一次且一次。一旦visit( )失败,则操作失败
7.1 图的定义和术语 V4 V2 V1 V3 V2 V3 V4 V1 V5 • 图的基本术语 • 1 邻接点及关联边 • 邻接点:边的两个顶点 • 关联边:若边e= (v, u), 则称顶点v、u 关联边e • (边e 依附于顶点v,u) • 2 顶点的度、入度、出度顶点V的度=与V相关联的边的数目 • 在有向图中: • 顶点V的出度=以V为起点有向边数 • 顶点V的入度=以V为终点有向边数 • 顶点V的度= V的出度+V的入度 • 设图G的顶点数为n,边数为e • 图的所有顶点的度数和 = 2*e • (每条边对图的所有顶点的度数和“贡献”2度) e1
3 有向完全图、无向完全图 有向完全图——n个顶点的有向图最大边数是n(n-1) 无向完全图——n个顶点的无向图最大边数是n(n-1)/2 权(weight)——与图的边或弧相关的数叫~ 网——带权的图叫~ 7.1 图的定义和术语
7.1 图的定义和术语 V4 V2 V3 V1 V2 V3 V4 V1 V5 • 路径、回路 • 无向图中的顶点序列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, 若<vi,vi+1>E ( i=1,2,…k-1), v =v1, u =vk, 则称该序列是从顶点v到顶点u的路径;若v=u,则称该序列为回路; • 例 • 在图1中,V1,V2,V3,V4 是V1到V4的路径; V1,V2,V3,V4,V1是回路;在图2中,V1,V3,V4 是V1到V4的路径; V1,V3,V4,V1是回路; 图1 图2
7.1 图的定义和术语 V4 V2 V2 V5 V6 V1 V3 V4 V1 V5 V3 • 连通图、(强连通图) • 在无(有)向图G=< V, E >中,若对任何两个顶点v、u都存在从v • 到u的路径,则称G是连通图(强连通图) 连通图 非连通图
7.1 图的定义和术语 V4 V4 V2 V2 V2 V3 V3 V3 V1 V1 V1 V5 V5 V5 • 子图 • 设有两个图G=(V,E)、G1=(V1,E1),若V1 V,E1 E,E1关联的顶点都在V1中,则称 G1是G的子图; • 例 图2、图3 是 图1 的子图 图2 图1 图3
7.1 图的定义和术语 V2 V5 V6 V1 V4 V3 • 连通分图(强连通分量) • 无向图G的极大连通子图称为G的连通分量 • 极大连通子图意思是:该子图是G连通子图,将G的任何不在该子图中的顶点加入,子图不再连通; • 有向图D的极大强连通子图称为D的强连通分量 • 极大强连通子图意思是:该子图是D强连通子图,将D的任何不在该子图中的顶点加入,子图不再是强连通的; 连通分图
7.1 图的定义和术语 V4 V4 V2 V2 V3 V3 V1 V1 V5 V5 8 生成树( 包含无向图G所有顶点的的极小连通子图称为G生成树 极小连通子图意思是:该子图是G的连通子图,在该子图中删除任何一条边,子图不再连通, 若T是G的生成树当且仅当T满足如下条件 T是G的连通子图 T包含G的所有顶点 T中无回路 只有足以构成一棵树的n-1条边
例 例 例 2 2 4 4 5 5 无向完全图 有向完全图 1 1 3 3 6 6 G1 2 2 1 3 1 3 例 1 5 7 图与子图 3 2 4 6 G2 5 顶点5的度:3 顶点2的度:4 顶点2入度:1 出度:3 顶点4入度:1 出度:0 3 6
例 2 4 5 1 3 6 G1 例 1 5 7 3 2 4 6 G2 路径:1,2,3,5,6,3 路径长度:5 简单路径:1,2,3,5 回路:1,2,3,5,6,3,1 简单回路:3,5,6,3 路径: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
例 2 4 5 1 3 6 例 例 2 4 5 5 1 3 6 3 6 连通图 强连通图 非连通图连通分量
第七 章 图 • 7.2 图的存储结构 • 数组表示法 • 邻接表
7.2 图的存储结构 图是多对多的结构,比线性结构、树结构复杂,所以其存储结构也要复杂些。与线性结构、树结构一样,图的存储结构至少要保存两类信息: 1)顶点的数据 2) 顶点间的关系 顶点的编号 为了使图的存储结构与图一一对应,在讨论图的存储结构时,首先要给图的所有顶点编号。 本课程介绍两类图的存储结构 数组表示法 邻接表(邻接表,逆邻接表) 设 G=<V, E>是图, V={v1,v2,v3, …vn },设顶点的的角标为它的编号 如何表示顶点间的关系? ?
7.2 图的存储结构 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]=
7.2 图的存储结构 0 1 0 1 0 1 0 1 0 1 0 1 2 3 4 m m+1m+2 m+3m+4 V1 V2 V3 V4 V5 0 1 2 3 4 5 m-1 数组表示法 顶点的存储:用一维数组存储(按编号顺序) 顶点间关系:用二维数组存储图的邻接矩阵; 存储顶点的 一维数组 存储邻接矩阵的 二维数组
7.2 图的存储结构 数组表示法类型定义 #define INFINITY INT_MAX #define MAX_VERTEX_NUM 20 typedef enum{DG, DN, AG, AN} GraphKind; typedef struct ArcCell { VRType adj; InfoType *info; }ArcCell, AdjMatrix[MAX_VERTEX_NUM ][MAX_VERTEX_NUM ] typedef struct { VetexType vexs[MAX_VERTEX_NUM ]; AdjMatrix arc; int vexnum, arcnum; GraphKind kind; }MGraph;
7.2 图的存储结构 顶点数组 V1 0 n e AG G.vexs G.arcs G.vexnumG.arcnu G.kind 存储邻接矩阵的二维数组 设G是Mgraph 类型的变量,用于存储无向图,该图有n个顶点,e条边 G的图示如下:
7.2 图的存储结构 无向图数组表示法特点: 1)无向图邻接矩阵是对称矩阵,同一条边表示了两次; 2)顶点v的度:等于二维数组对应行(或列)中1的个数; 3)判断两顶点v、u是否为邻接点:只需判二维数组对应分量是否为1; 4)顶点不变,在图中增加、删除边:只需对二维数组对应分量赋值1或清0; 5)设存储顶点的一维数组大小为m(m图的顶点数n), G占用存储空间:m+m2;G占用存储空间只与它的顶点数有关,与边数无关;适用于边稠密的图; 对有向图的数组表示法可做类似的讨论
7.2 图的存储结构 该结点表示边 (V1 V2),其中的1是V2在一维数组中的位置 0 1 2 3 4 m-1 V1 V2 V3 V4 V5 4 0 V4 V2 V3 1 3 0 4 1 3 2 V1 1 2 V5 • 邻接表 • 邻接表是图的链式存储结构 • 1 无向图的邻接表 • 顶点:通常按编号顺序将顶点数据存储在一维数组中; • 关联同一顶点的边:用线性链表存储 例
7.2 图的存储结构 图的邻接表类型定义 #define MAX_VERTEX_NUM 20 typedef struct ArcNode{ //边(弧)结点的类型定义 int adjvex; //边(弧)的另一顶点的在数组中的位置 struct ArcNode *nextarc; //指向下一条边(弧)结点的指针 InfoType *info; }ArcNode; typedef struct VNode { //顶点结点和数组的类型定义 VertexType data; //顶点信息 ArcNode * finrstarc; //指向关联该顶点的边(弧)链表 }VNode, AjList[MAX_VERTEX_NUM]; typedef struct { AdjList vertices; int vexnum, arcnum; //图的当前顶点数和弧数 int kind; //图的种类标志 }ALGraph;
7.2 图的存储结构 0 1 2 4 m-1 data firstarc adjvex nextarc 4 0 V4 V2 V3 G.vertices G.vexnum G.arcnu G.kind V1 V2 V3 V4 V5 1 3 0 4 1 3 2 V1 1 2 V5 n e AG 设G是ALGraph 类型的变量,用于存储无向图G1,该图有n个顶点,e条边 G的图示如下: 该结点表示边 (V5,V2),其中的1是V2在一维数组中的位置 无向图G1
7.2 图的存储结构 无向图的邻接表的特点 1)在G邻接表中,同一条边对应两个结点; 2)顶点v的度:等于v对应线性链表的长度; 3)判定两顶点v ,u是否邻接:要看v对应线性链表中有无对应的结点 4)在G中增减边:要在两个单链表插入、删除结点;
7.2 图的存储结构 V1 V2 V3 V4 0 1 3 2 有向图的邻接表和逆邻接表 1)有向图的邻接表 顶点:用一维数组存储(按编号顺序) 以同一顶点为起点的弧:用线性链表存储 D.vertices D.vexnum D.arcnu D.kind V1 V2 V3 V4 2 例 类似于无向图的邻接表, 所不同的是: 以同一顶点为起点的弧: 用线性链表存储 n e DG
7.2 图的存储结构 D.vertices D.vexnum D.arcnu D.kind V1 V2 V3 V4 V1 V2 V3 V4 3 0 0 2 n e DG 2)有向图的逆邻接表 顶点:用一维数组存储(按编号顺序) 以同一顶点为终点的弧:用线性链表存储表 类似于无向图的邻接表, 所不同的是: 以同一顶点为终点的弧: 用线性链表存储
7.2 图的存储结构 在不同的存储结构下,实现各种操作的效率可能是不同的。所以在求解实际问题时,要根据求解问题所需操作,选择合适的存储结构。
第七 章 图 7.3 图的遍历 一 深度优先遍历 二 广度优先遍历
7.3 图的遍历 图的遍历:从图的某顶点出发,访问图中所有顶点,并且每个顶点仅访问一次。 图中可能有回路,遍历可能沿回路又回到已遍历过的结点。为避免同一顶点被多次访问,必须为每个被访问的顶点作一标志。为此引入一辅助数组, 记录每个顶点是否被访问过。 有两种遍历方法(它们对无向图,有向图都适用): 深度优先遍历 广度优先遍历
7.3 图的遍历 V3 V5 V8 V2 V1 V6 V4 V7 一 深度优先遍历 从图中某顶点v出发: 1)访问顶点v;2)从v的未被访问的邻接点出发,继续对图进行深度优先遍历; 注:为简单起见, 只讨论非空连通图的遍历 这是序列(1)在遍历过程中所经过的路径 例图G中,以V1起点的的深度优先序列: (1) V1,V2,V4,V5,V8,V3,V6,V7, (2) V1,V2,V5,V8,V4,V3,V6,V7 由于没为有规定 访问邻接点的顺序, 深度优先序列不是唯一的
7.3 图的遍历 V3 V5 V8 V2 V1 V6 V4 V7 深度优先遍历(设图为非空连通图) 从图中某顶点v出发: 1)访问顶点v;2)从v的未被访问的邻接点出发, 继续对图进行深度优先遍历; 先序遍历(DLR) 若二叉树非空 (1)访问根结点; (2)先序遍历左子树; (3)先序遍历右子树; 如果将图顶点的邻接点 看作二叉树结点的左、右孩子 深度优先遍历与先序遍历 是不是很类似?
7.3 图的遍历 0 0 0 0 0 visited 0 1 2 3 4 m-1 Boolean visited[MAX_VERTEX_NUM] //访问标志数组,全局变量,初始值:所有分量全为False(0) //visited[v]=TRUE表示顶点v已被访问 深度优先遍历算法 void DFS(Graph G, int v, Status(*Visit(int v)) { // 从第v个顶点出发,递归地深度优先遍历图G。 // v是顶点在一维数组中的位置,假设G是非空图 visited[v] =TRUE; Visit(v); //访问第v个顶点 for (w= FirstAdjVex(G, v); w; w=NextAdjVex(G, v, w)) if (!visited[w]) DFS(G, w); //对v的尚未访问的邻接顶点w递归调用DFS
7.3 图的遍历 深度优先遍历算法 void DFS(Graph G, int v, Status(*Visit(int v)) { // 从第v个顶点出发,递归地深度优先遍历图G。 // v是顶点在一维数组中的位置,假设G是非空图 visited[v] =TRUE; Visit(v); //访问第v个顶点 for (w= FirstAdjVex(G, v); w; w=NextAdjVex(G, v, w)) if (!visited[w]) DFS(G, w); //对v的尚未访问的邻接顶点w递归调用DFS 先序遍历递归算法void PreOrderTraverse(BiTree T, Status(*Visit)(TElemType e)) {//本算法先序遍历以T为根结点指针的二叉树。 if (T) { //若二叉树为空,结束返回 Visit(T->data); PreOrderTraverse(T->lchild, Visit); PreOrderTraverse(T->rchild, Visit); }//PreOrderTraverse 如果将图顶点的邻接点 看作二叉树结点的左、右孩子 深度优先遍历算法与 先序遍历算法 的结构是不是很像?
7.3 图的遍历 V3 V5 V8 V2 V1 V6 V4 V7 • 广度优先遍历(类似于树的按层遍历) • 从图中某顶点v出发: • 1)访问顶点v ; • 2)访问v的所有未被访问的邻接点w1 ,w2 ,…wk ; • 3)依次从这些邻接点出发,访问它们的所有未被访问的邻接点; 依此类推,直到图中所有访问过的顶点的邻接点都被访问; • 例图G中,以V1起点的的广度优先序列: • (1)V1,V2,V3,V4,V5,V6,V7,V8 • (2)V1,V3,V2,V6,V7,V4,V5,V8 这是序列(1)在遍历过程中所经过的路径 由于没为有规定 访问邻接点的顺序, 广度优先序列不是唯一的
7.3 图的遍历 广义优先遍历算法 从图中某顶点v出发: 1)访问顶点v ;(容易实现) 2)访问v的所有未被访问的邻接点w1 ,w2 ,…wk ; (容易实现) 3)依次从这些邻接点(在步骤 2)访问的顶点)出发,访问它们的所有未被访问的邻接点; 依此类推,直到图中所有访问过的顶点的邻接点都被访问; 为实现 3),需要保存在步骤(2)中访问的顶点,而且访问这些顶点邻接点的顺序为:先保存的顶点,其邻接点先被访问。 在广度优先遍历算法中,需设置一队列Q, 保存已访问的顶点,并控制遍历顶点的顺序。
7.3 图的遍历 void BFSTraverse(Graph G,int v,Status (* Visit)(int v)) { //从v出发,广度优先遍历连通图G。 v是顶点在一维数组中的位置,使用辅助队列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; w=NextAdjVex(G,u,w)) if(!visited[w]){ // 若w尚未访问 Visited[w]=TRUE; Visit(w);EnQueue(Q,w); }//if }//while }//BFSTraverse
7.3 图的遍历 V2 V2 V1 V5 V6 V1 V5 V6 V4 V4 V3 V3 非连通图的遍历 上面介绍了连通图的深度优先遍历算法,对非连通图,可从每个连通分量选一起点,调用遍历算法(如DFS)完成各连通分量的深度先遍历。 例 图G的深度优先序列:V1,V2,V3,V4,V5,V6, 广度优先序列 V1,V2,V3,V4,V5,V6, 深度优先遍历所经过的路径 广度优先遍历所经过的路径
7.4.1 图的连通性问题--无向图的连通分量和生成树 生成树 定义:所有顶点均由边连接在一起,但不存在回路的图叫~ 深度优先生成树与广度优先生成树 生成森林:非连通图每个连通分量的生成树一起组成非连通图的~ 7.4 图的连通性
7.4.1 图的连通性问题--无向图的连通分量和生成树 说明 一个图可以有许多棵不同的生成树 所有生成树具有以下共同特点: 生成树的顶点个数与图的顶点个数相同 生成树是图的极小连通子图 一个有n个顶点的连通图的生成树有n-1条边 生成树中任意两个顶点间的路径是唯一的 在生成树中再加一条边必然形成回路 含n个顶点n-1条边的图不一定是生成树 G K I H
V1 V1 V2 V3 V2 V3 V4 V5 V6 V7 V4 V5 V6 V7 V1 V8 V1 V8 V2 V3 V2 V3 V4 V6 V4 V5 V6 V7 V1 例 V8 V7 V2 V3 V8 广度优先生成树 V5 V4 V5 V6 V7 深度优先生成树 V8 深度遍历:V1 V2 V4 V8 V5 V3 V6 V7 广度遍历:V1 V2 V3 V4 V5 V6 V7 V8
例 A B C D E F G H D A I K L C F E J M L M B J G K I 深度优先生成森林 H
void DFSTree(Graph G,int v ,CSTree *T) {/*从第v个顶点出发深度优先遍历图G,建立以*T为根的生成树*/ visited[v]=TRUE; first=TRUE; for(w=FirstAdjVex(G,v); w; w=NextAdjVex(G,v,w)) if(!visited[w]) { p=(CSTree)malloc(sizeof)CSNode)); /*分配孩子结点*/ *p={GetVex(G,w),NULL,NULL}; if (first) /*w是v的第一个未被访问的邻接顶点,作为根的左孩子结点*/ { T->lchild=p; first=FALSE; } else { /*w是v的其它未被访问的邻接顶点,作为上一邻接顶点的右兄弟*/ q->nextsibling=p; } q=p; DFSTree(G,w,&q); /*从第w个顶点出发深度优先遍历图G,建立生成子树*q*/ } }
7.4 图的连通性 • 7.4.3 最小生成树 • n个城市之间,最多可能设置n(n-1)/2条线路,而连通n个城市只需要n-1条线路. • 最小(代价)生成树的定义. • 一棵生成树的代价.