630 likes | 776 Views
6 图 (1). 教学目标. 熟练掌握图的概念和术语,图的邻接矩阵与邻接表两种存储方法。 熟练掌握图的遍历的概念,图的 DFS 与 BFS 方法与算法。 掌握最小生成树的概念,构造最小生成树的方法,求图的最小生成树的算法。 掌握最短路径的概念,图的单源点最短路径的求解方法。了解单源点最短路径的算法; 掌握拓扑排序和关键路径的方法 , 了解拓扑排序和关键路径的算法。. 教学内容. 6.1 图的定义和术语 6.2 图的存储表示 6.3 图的遍历 6.4 最小生成树 6.5 两点之间的最短路径问题 6.6 拓扑排序 6.7 关键路径. 问题回顾.
E N D
教学目标 熟练掌握图的概念和术语,图的邻接矩阵与邻接表两种存储方法。 熟练掌握图的遍历的概念,图的DFS与BFS方法与算法。 掌握最小生成树的概念,构造最小生成树的方法,求图的最小生成树的算法。 掌握最短路径的概念,图的单源点最短路径的求解方法。了解单源点最短路径的算法; 掌握拓扑排序和关键路径的方法,了解拓扑排序和关键路径的算法。
教学内容 6.1 图的定义和术语 6.2 图的存储表示 6.3 图的遍历 6.4 最小生成树 6.5 两点之间的最短路径问题 6.6 拓扑排序 6.7 关键路径
6.1 图的定义和术语 图的定义:图是由一个顶点集 V 和一个边集 E构成的数据结构。 G = (V , E ) 其中:G表示一个图,V是图G中顶点的集合,E是图G中顶点之间边的集合。 若顶点v和w之间的边没有方向,则称这条边为无向边,表示为(v, w)。相应的图称为无向图。 若从顶点v到w的边有方向,则称这条边为有向边(又称为弧),表示为<v, w>。相应的图称为有向图。
例如:G1 = (V1, E1) 是有向图 其中 V1={A, B, C, D, E} E1={<A,B>, <A,E>, <B,C>, <C,D>, <D,B>, <D,A>, <E,C> } A B E C D B C A D F E 例如: G2=(V2,E2) 为无向图 V2={A, B, C, D, E, F} E2={(A,B), (A,E),(B,E), (C,D), (D,F),(B,F), (C,F) }
名词和术语 网、子图 完全图、稀疏图、稠密图 邻接点、度、入度、出度 路径、路径长度、简单路径、简单回路 连通图、连通分量、 强连通图、强连通分量 生成树、生成森林
A 9 15 带权的图分别称作有向网或无向网。 11 B E 7 21 3 C F 2 B 设图G=(V,E) 和图G=(V,E), 且VV, EE, 则称G 为 G 的子图。 A B E C F
假设图中有 n个顶点,e条边,则 含有 e=n(n-1)/2 条边的无向图称作完全图; 含有 e=n(n-1)条弧的有向图称作 有向完全图; 若边或弧的个数 e < nlogn,则称作稀疏图,否则称作稠密图。
对无向图来说, 假若顶点v 和顶点w 之间存在一条边, 则称顶点v和w互为邻接点, 边(v,w)和顶点v 和w相关联。 和顶点v 关联的边的数目定义为顶点的度。 B C 例如: D TD(B) = 3 A TD(A) = 2 E F
对有向图来说, A 顶点的出度: 以顶点v为弧尾的弧的数目; B E C D 顶点的入度: 以顶点v为弧头的弧的数目。 例如: OD(B) = 1 顶点的度(TD)= 出度(OD)+入度(ID) ID(B) = 2 TD(B) = 3
设图G=(V,E)中的一个顶点序列 { u=vi,0,vi,1, …, vi,m=w}中,(vi,j-1,vi,j)E ,1≤j≤m, 则称从顶点u 到顶点w 之间存在一条路径。 路径上边的数目称作路径长度。 如:长度为3的路径{A,B,C,D} 简单路径:序列中顶点不重复出现的路径。 A 简单回路:序列中第一个顶点和最后一个顶点相同的路径。 E B C D
对无向图 C B 若(右)图G中任意两个顶点之间都有路径相通,则称此图为连通图; D A E F C B D 若(左)无向图为非连通图,则图中各个极大连通子图称作此图的连通分量。 A E F
若任意两个顶点之间都存在一条有向路径,则称此有向图为强连通图。若任意两个顶点之间都存在一条有向路径,则称此有向图为强连通图。 对有向图, 否则,其各个强连通子图称作它的强连通分量。 A A B E B E C D C D
假设一个连通图有 n 个顶点和 e 条边,其中 n-1 条边和 n 个顶点构成一个极小连通子图(生成树)。 对非连通图,则称由各个连通分量的生成树的集合为此非连通图的生成森林。 C B D A E F
部分操作 • 结构的建立 • 对顶点的操作 • 插入和删除弧 • 遍历
结构的建立 CreatGraph(&G, V, E) // 按定义(V, VR) 构造图 对顶点的操作 LocateVex(G, u); // 若G中存在顶点u,则返回该顶点在图中“位置” 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); //从顶点v开始深度优先遍历图G,并对访问每个顶点一次且仅一次。 BFSTraverse(G, v); //从顶点v开始广度优先遍历图G,并对访问每个顶点一次且仅一次。
A B F E C D V2 V1 线性结构 V3 A V5 V4 B C D E F 图结构 树结构 不同结构中逻辑关系的对比 在线性结构中,数据元素之间仅具有线性关系; 在树结构中,结点之间具有层次关系; 在图结构中,任意两个顶点之间都可能有关系。
A B F E C D V2 V1 线性结构 V3 A V5 V4 B C D E F 图结构 树结构 不同结构中逻辑关系的对比 在线性结构中,元素之间的关系为前驱和后继; 在树结构中,结点之间的关系为双亲和孩子; 在图结构中,顶点之间的关系为邻接。
名词和术语复习 网、子图 完全图、稀疏图、稠密图 邻接点、度、入度、出度 路径、路径长度、简单路径、简单回路 连通图、连通分量、强连通图、强连通分量 生成树、生成森林
6.2 图的存储表示 • 图的数组(邻接矩阵)存储表示 • 图的邻接(链)表存储表示
一、图的数组(邻接矩阵)存储表示 0 (i,j)E 定义:矩阵的元素为 Aij={ 1 (i,j)E A B C D E F B C A B C D E F A D F E
无向图数组表示法特点: • 无向图邻接矩阵是对称矩阵,同一条边表示了两次; • 顶点v的度:等于二维数组对应行(或列)中1的个数; • 判断两顶点v、u是否为邻接点:只需判二维数组对应分量是否为1; • 在图中增加、删除边:只需对二维数组对应分量赋值1或清0; • 占用存储空间只与它的顶点数有关,与边数无关;适用于边稠密的图; • 对有向图的数组表示法可做类似的讨论
有向图的邻接矩阵为非对称矩阵 A B C D E A B C D E A B E C D
structArcCell { // 弧的定义 VRType adj; // VRType是顶点关系类型。 // 无权图:0或1,有权图:权值 InfoType *info; // 该弧相关信息(可选) }; enum GraphKind{DG, DN, UDG, UDN}; //有向图, 有向网, 无向图, 无向网 struct MGraph{ // 图的定义 VertexType vexs[MaxVN]; // 顶点信息 ArcCell arcs[MaxVN][MaxVN]; // 弧的信息 int vexnum, arcnum; // 顶点数,弧数 enumGraphKindkind; // 图的种类标志 };
常见的简单描述如下: struct MGraph{ // 图的定义 int mat[MaxVN][MaxVN]; // 边、弧的信息 int vexnum; // 顶点数 GraphKind kind; // 图的种类标志 };
0 A 1 4 1 B 0 4 5 2 C 3 5 3 D 2 5 4 E 0 1 5 F 1 2 3 二、图的邻接 (链)表存储表示 B C A D F E
A B C D E 0 1 2 3 4 1 4 2 3 0 1 2 有向图的邻接表 A B E C F 注意,在有向图的邻接表中不易找到指向该顶点的弧。
A 有向图的逆邻接表 B E C D 在有向图的邻接表中,对每个顶点,链接的是指向该顶点的弧。 A B C D E 3 0 1 2 3 4 3 0 4 2 0
弧的结点结构 adjvex info nextarc struct ArcNode { int adjvex(data); // 该弧所指向的顶点的位置 ArcNode *nextarc(next); // 指向下一条弧 InfoType *info; // 该弧相关信息(可选) }; 顶点的结点结构 data firstarc struct VNode { VertexType data; // 顶点信息 ArcNode *firstarc; // 指向第一条依附该顶点的弧 };
图的定义 struct ALGraph{ // Adjacent List VNodevertices[MaxVN]; int vexnum, arcnum; enum GraphKind kind; // 图的种类标志 };
无向图邻接表的特点 • 在邻接表中,同一条边对应两个结点; • 顶点v的度:等于v 对应的链表的长度; • 判定两顶点v,w是否邻接:要看v对应的链表中有无对应的结点w(相反判断也行); • 增减边:要在两个单链表插入、删除结点; • 占用存储空间与顶点数、边数均有关;适用于边稀疏的图
建立邻接表算法描述 // 初始化一个结点总数为num的图, k为图的类型, num 为结点总数 void InitG(ALGraph G, enum GraphKind k, int num){ G.kind=k; G.vexnum=num; G.vertices=new VNode[vexnum]; for(int i=0; i<G.vexnum; i++) { G.vertices[i]. Firstarc=NULL; cin>>G.vertices[i]. data; // 输入结点信息 } } // 有向图(网)增加弧的算法, 将弧(from,to,weight)加入图 void InsertArc(ALGraph G, int from, int to, int weight){ ArcNode *s=new ArcNode; s->weight = weight; s->adjvex = to; s->nextarc = G.vertices[from].firstarc; // 插到链表vertices[from]的头 G.vertices[from].firstarc=s; } 显然,对于无向图(网),只需交换from和to的位置,再进行一次增加弧的操作即可
课内练习 1、分析并回答下列问题: 1) 图中顶点的度之和与边数之和的关系? 2) 有向图中顶点的入度之和与出度之和的关系? 3) 具有n个顶点的无向图,恰好有n-1条边时一定连通吗? 4) 具有n个顶点的无向图,至少应有多少条边(可能/一定)是一个连通图? 5) 具有n个顶点的有向图,至少应有多少条弧(可能/一定)是一个强连通图?
课内练习 2、设一有向图G=(V,E),其中V={a,b,c,d,e} , E={<a,b>, <a,d>, <b,a>, <c,b>, <c,d>, <d,e>,<e,a>, <e,b>, <e,c>} 1) 请画出该有向图,并求各顶点的入度和出度。 2) 画出有向图的邻接链表。 3) 画出有向图的逆邻接链表。
课内思考 小学生游戏 某天,无聊的小杰叫上几个同学玩游戏,其中有比较笨的小凤,比较傻的小雪,可爱的小鑫和自以为是的小练。他们去找聪明的小艺去给他们当裁判。判定谁取得游戏胜利。 而这个游戏是:由小艺给出一个数 a ,再给出一个数 b ,经过规定的运算,使得数 a 变换成数 b ,且使用最少的变换次数 n .谁先说对这个 n ,谁就取得胜利。当然,因为都是小学生,所以假定如果n>6 ,就算是没有答案。那么裁判小艺试图通过编程来使自己尽快的获得答案。请你帮帮他吧...... 问题描述: 题目给出数a(a是一个正整数,不超过50位),再给出目标数b(同样是一个正整数,不超过50位),数的运算有三种:1:使当前数加上19854292:使当前数加上20063:使当前数乘2
6.3 图的遍历 从图中某个顶点出发,访遍图中其余顶点,并且使图中的每个顶点仅被访问一次的过程。 • 深度优先搜索 • 广度优先搜索 • 遍历应用举例
一、深度优先搜索遍历图 连通图的深度优先搜索遍历 从图中某个顶点V0 出发,访问此顶点,然后依次从V0的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和V0有路径相通的顶点都被访问到。
W1、W2和W3均为 V 的邻接点,SG1、SG2 和 SG3 分别为含顶点W1、W2和W3的子图。 V w1 w3 w2 w2 SG3 SG1 SG2 访问顶点 V : foreach (W1、W2、W3 ) 若该邻接点W未被访问, 则从它出发进行深度优先搜索遍历。
从上页的图解可见: 1. 从深度优先搜索遍历连通图的过程类似于树的先根遍历; 2. 如何判别V的邻接点是否被访问? 解决的办法是:为每个顶点设立一个 “访问标志数组bool visited[vexnum]”。
void DFS(Graph G, int v) { // 从顶点v出发,深度优先搜索遍历连通图 G visited[v] = true; cout<< G.vertices[v].data; for(w=FirstAdjVex(G, v);w存在;w=NextAdjVex(G,v,w)) if (!visited[w]) DFS(G, w); // 对v的尚未访问的邻接顶点w,递归调用DFS } // DFS void DFS(Graph G, int v) { visited[v] = true; cout<< G.vertices[v].data; for(w=0; w<G.vexnum; w++) if (mat[v][w] && !visited[w]) DFS(G, w); } // DFS 若采用邻接矩阵,则算法可写成右边的形式:
非连通图的深度优先搜索遍历 先将每个顶点的访问标志设为 false, 之后搜索图中每个顶点,若未被访问,则以该顶点为起始点,进行深度优先搜索遍历,否则继续检查下一顶点。 void DFSTraverse(Graph G ) {// 深度优先遍历 fill(visited, visited+G.vexnum, false); // 访问标志数组初始化 for (v=0; v<G.vexnum; ++v) if (!visited[v]) DFS(G, v); // 对尚未访问的顶点调用DFS } 本算法对所有类型的图均适合
例如: a a b b g g f c c d d e e f h h k k 0 1 2 3 4 5 6 7 8 访问标志: F F F F F F F F F T T T T T T T T T 访问次序: g k e a c h d f b
二、广度优先搜索遍历图 对连通图,从起始点V到其余各顶点必定存在路径。 w2 w2 w1 w1 其中,V->w1, V->w2, V->w8 的路径长度为1; V V w3 w3 w7 w7 V->w7, V->w3, V->w5 的路径长度为2; w8 w8 w4 w4 w6 w6 V->w6, V->w4 的路径长度为3。 w5 w5
从图中某个顶点V0出发,并在访问此顶点之后依次访问V0的所有未被访问过的邻接点,之后按这些顶点被访问的先后次序依次访问它们的邻接点,直至图中所有和V0有路径相通的顶点都被访问到。从图中某个顶点V0出发,并在访问此顶点之后依次访问V0的所有未被访问过的邻接点,之后按这些顶点被访问的先后次序依次访问它们的邻接点,直至图中所有和V0有路径相通的顶点都被访问到。 对于非连通图,可能此时尚有顶点未被访问,则另选图中一个未被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。
void BFSTraverse(Graph G){ fill(visited, visited+G.vexnum, false); //初始化visited数组 for (int v=0; v<G.vexnum; ++v ){ if (visited[v]== true ) continue; // v 已经访问 visited[v] = true; cout<<G.vertices[v].data;EnQueue(Q, v); // v入队列 while (!Empty(Q)) { DeQueue(Q, u); // 队头元素出队并置为u foreach ( u的邻接点w ){ if (visited[w] == false){ visited[w]=true; Visit(w); EnQueue(Q, w); } } } } } // BFSTraverse
也可写成: void BFSTraverse(Graph G) { fill(visited, visited+G.vexnum, false); //初始化visited数组 for (int v=0; v<G.vexnum; ++v ){ if (visited[v] == false) EnQueue(Q, v); // v入队列 while (!Empty(Q)) { DeQueue(Q, u); // 队头元素出队并置为u visited[u]=true; cout<< G.vertices[u].data; foreach ( u的邻接点w ){ if (visited[w] == false) EnQueue(Q, w); } } } } // BFSTraverse