1、软件技术基础,制作 主讲,段景山,段景山,图结构,第四章 图结构,图的基本概念,图的遍历,图的存储,4.1图的定义和术语 图是由非空的顶点组成的有限集和边的有限集组成。 二元组 G = ( V,E) V:顶点集合 E:边的集合,即顶点间的关系 图结构中的关系可以是任意的,甚至可以是空集 图是一种对结点的前驱和后继个数不加限制的数据结构,图的定义、术语,1)有向图与无向图 由有向边组成的图,称为有向图 有向边以表示,又称为弧 X:弧尾 Y:弧头 有向边与不是同一条边 由无向边组成的图,称为无向图 无向边以(X,Y)表示 无向边(X,Y)与(Y,X)是同一条边,图的术语,图的术语,2)权与网 图中
2、边或弧所具有的相关数称为权 一般用于表明从一个顶点到另一个顶点的距离或耗费 带权的图称为网 3)邻接与顶点的度 对于(X,Y)E 则X ,Y互为邻接 对于 E 则Y是X的邻接结点,13,图的术语,顶点的度课堂练习,给出以下顶点的度,顶点度为,有向图:,出度:,入度:,以该顶点为弧尾的弧数,以该顶点为弧头的弧数,无向图,:顶点的边数,总边数 ,2,1,D(Vi),i=1,n,和,2,1,各顶点度之和,图的术语,4)路径与回路 路径:图中沿着各条边,从X到Y所经历的顶点序列称为路径 路径:X , b , a , Y 环:第一顶点与最后一个顶点相同的路径称为环 环:X,a,Y,b,X 回路: 序列中
3、不出现重复的路径称为简单路径 有重复的路径称为回路 回路:X,a,b,a,Y,a,X,Y,b,图的术语,5)连通、连通图与连通分量 连通:在图中若X与Y之间有路径,则称X,Y是连通的 连通图:任意两个顶点都连通的图称为连通图 没有孤立顶点 连通分量:指无向图中极大连通子图,即有多少个连通子图,图的存储 邻接矩阵,4.2 图的存储 4.2.1 邻接矩阵法 (1)给顶点编号 (2)建立邻接(关系)矩阵,1 2 3 4,1 2 3 4,0 0 0 0,1 0 1 1,1 0 0 1,0 1 1 0,a i j表示弧 1:表示有弧;0:表示无弧,任意顶点的出度是该行元素之和 任意顶点的入度是该列元素之
4、和,图的存储 邻接矩阵,4.2.1 邻接矩阵法若边 顶点2 则邻接矩阵为稀疏矩阵。 邻接矩阵的优点:增减边的操作简单 邻接矩阵的缺点: 增减顶点的操作需要搬移大量的元素, 浪费存储空间,1 2 3 4,1 2 3 4,0 1 1 0,1 0 1 1,1 1 0 1,0 1 1 0,无向图的邻接矩阵是对称的,图的存储 邻接矩阵的实现,图的邻接矩阵实现,typedef struct graph_melemtype nodeMAXNUM;int arcsMAXNUMMAXNUM; graph_m;,顶点集合,边的邻接矩阵,二维数组,图的存储 邻接表,4.2.2 邻接表法一个邻接表由两种结构组成 存放
5、各顶点元素的数组,头结点 各顶点各自的邻接链表,邻接结点,3,data,顶点号,元素域,邻接链表头指针,邻接顶点号,下一邻接结点,图的存储(课堂练习),请写出下面这副图的邻接表 1、给顶点编号 2、建立顶点数组 3、建立各顶点的邻接链表 注意,此图为有向图,2,1,3,4,5,图的存储 邻接表的实现,邻接表的定义 头结点的定义邻接结点的定义,typedef struct headnodeint node_index;elemtype data;struct adj_node * next_adj; headnode;,typedef struct adj_nodeint node_index;
6、struct adj_node *next_adj; adj_node;,图的存储 邻接表,图邻接表的定义,typedef struct graph_lheadnode node_listMAXNUM;int node_num; graph_l;,图的存储 邻接表,2,1,4,3,2,1,4,3,注意:有向图与无向图的区别,图的存储,图的邻接表存储法的特点 优点 节省存储空间 边的插入和删除操作比较简便 缺点 结构复杂 具有两种不同的基本组成单元,图的存储,4.2.3边带权值的图的存储 1)在邻接矩阵中的实现,0 2 3 5 0 1 0 1 0 1 5 0 1 0,a44 =,a i j :记
7、录边的权值;为0表示无边,2,3,5,1,1,图的存储,4.2.3边带权值的图的存储 2)在邻接表中的实现 在邻接结点结构中增加一个权值域,顶点号,边权值,1,2,4,3,3,2,5,1,1,10,3,图的遍历,4.3图的遍历 问题: 1、对于连通图,从一个顶点出发沿着所有可能的路径,是否可以将所有的顶点遍历到。 2、图中有回路,遍历算法可能产生死循环。,图的遍历 深优,4.3.1 深度优先遍历 (1)从一个未访问过的顶点开始,访问它的一个未访问过的相邻顶点。 (2)如果顶点的所有相邻顶点都是已访问过的,则需要回溯到之前的一个顶点,选取它的另一个未被访问的相邻顶点。 (3)对于前两个步骤是递归
8、的,图的遍历 深优,4.3.1 深度优先遍历 (1)从一个未访问过的顶点开始,访问它的一个未访问过的相邻顶点。 (2)如果顶点的所有相邻顶点都是已访问过的,则需要回溯到之前的一个顶点,选取它的另一个未被访问的相邻顶点。 (3)对于前两个步骤是递归的,1,2,5,8,4,6,3,7,1,7,3,6,5,2,8,4,1,7,3,6,5,2,8,4,1 3 6 8 4 2 5 7,或者按以下顺序遍历:,注意使用栈支持递归:,图的遍历 深优,深度优先遍历的特点 “深度”:总是访问顶点的一个相邻顶点,好像是沿着图中的一条路径走到“底”,然后后退一点,再选一条路。如此“进进退退”,直到所有顶点都被访问 对
9、于连通图,如果第一个被访问的顶点的所有相邻顶点都被访问了,意味着图中所有顶点都被访问了。 即栈空时,算法实现分析 (1)需要记录结点的访问状态。 int visited 顶点号 ; (2)需要进行回溯。 lstack_type stack (3)图以邻接表的方式存储 graph_l graph ; (4)算法框架 (5)核心算法,图的遍历 深优,图的遍历 深优,(4)算法框架 利用栈,不断将顶点的相邻顶点入栈、访问、出栈 算法以栈空结束 框架:,第一个被访问的顶点入栈;,while(!empty(stack)核心算法;(即顶点被访问、出入栈的过程); ,图的遍历 深优,(5)核心算法,2、找到
10、该顶点的一个未被访问过的相邻顶点,3、若找到就将该顶点入栈,并访问,回到1,4、否则进行回退出栈,回到1,1、从一个顶点开始,p = graph.node_list v ;,adjp = p-next_adj; while( adjp != NULL )if ( visited adjp-node_index = = true )adjp = adjp-next_adj;elsebreak; ,if ( adjp != NULL)v = adjp-node_index;visit(v);visited v = true;push(stack , adjp-node_index); ,elsev
11、 = pop(stack);,void dfs( graph, v )int visitedMAXNUM;initialize visited ;create_stack(stack);visit( v );visited v = true;push( stack , v );,while( !empty (stack )p = graph-node_list v ;adjp = p-next_adj;while( adjp != NULL)if(visited adjp-node_index = true)adjp = adjp-next_adj;elsebreak;,if(adjp !=
12、NULL )v = adjp-node_index;visit( v );visited v = true;push( stack , v ); elsev = pop(stack); ,经过检查/调试上述算法有误,问题出在哪里?,在“入栈并访问”的实现 谁该入栈,谁该被访问?,当前节点入栈(为回退做准备),访问它的相邻未被访问过节点,正确做法是:,void dfs( graph, v )int visitedMAXNUM;initialize visited ;create_stack(stack);visit( v );visited v = true;push( stack , v );
13、,while( !empty (stack )p = graph-node_list v ;adjp = p-next_adj;while( adjp != NULL)if(visited adjp-node_index = true)adjp = adjp-next_adj;elsebreak;,if(adjp != NULL )push( stack , v );v = adjp-node_index;visit( v );visited v = true; elsev = pop(stack); ,图的遍历 深优,用递归调用实现深度优先遍历算法,void dfs (g,v),1访问顶点v
14、;,visit(v);visited v = 1;,p = gv-next_adj while(p != NULL)w = p-node_index;if(visitedw = 0)p = p-next_adj; ,2取得顶点的一个未被访问过的顶点w;,3 dfs(g,w);回到2;,4重复2,3直到该顶点所有的相邻顶点都被访问过;,dfs(g,w);,void dfs(g ,v)visited v = 1;p = g v -next_adjwhile(p != NULL)w = p-node_index;if(visited w = 0)dfs( g, w );p = p-next_adj;
15、,void dfs(g ,2)visited 2 = 1;p = g 2 -next_adjwhile(p != NULL)w = p-node_index;if(visited 3 = 0)dfs( g, 3 );p = p-next_adj; ,void dfs(g ,3)visited 3 = 1;p = g 3 -next_adjwhile(p != NULL)w = p-node_index;if(visited 4 = 0)dfs( g , 4 );p = p-next_adj; ,void dfs(g ,4)visited 4 = 1;p = g 4 -next_adjwhile
16、(p != NULL)w = p-node_index;if(visited w = 0)dfs( g , w );p = p-next_adj; ,void dfs(g ,5)visited 5 = 1;p = g 5 -next_adjwhile(p != NULL)w = p-node_index;if(visited w = 0)dfs( g , w );p = p-next_adj;,void dfs(g ,1)visited 1 = 1;p = g 1 -next_adjwhile(p != NULL)w = p-node_index;if(visited 2 = 0)dfs( g
17、, 2 );p = p-next_adj; ,if(visited 5 = 0),dfs( g , 5 );,图的遍历 广优,4.3.2、广度优先遍历 访问顶点v后,接着依次访问v的所有邻接顶点,再依次访问这些邻接顶点的邻接顶点。直到所有的顶点都被访问过。,1,2,5,8,4,6,3,7,1,2,3,4,5,6,7,8,注意:8是作为哪个顶点的邻接顶点被访问的?,1,2,3,4,5,6,7,8,体会队列操作方式:,图的遍历 广优,广度优先遍历的特点 “广度”:总是在访问了一个顶点后,依次访问它的所有相邻顶点。然后再从它的第一个相邻顶点开始,访问其所有的相邻顶点。如此逐个顶点地逐步扩散,直到所有
18、的顶点都被访问。 广度优先遍历操作具有队列操作的特点,当从队列中取出最后一个顶点,发现该顶点的所有相邻顶点都被访问过时,意味着图中所有的顶点都已被访问过,图的遍历 广优,算法实现分析 (1)需要纪录结点访问状态。 int visited顶点号 ; 数组 (2)需要队列辅助操作 lqueue_type queue; (3)图采用邻接表存储方式 graph_l graph; (5)算法框架 (4)核心算法,图的遍历 广优,(4)算法框架 利用队列,不断将顶点出队、入队,直到所有的顶点都被访问过 当队列为空时,算法结束 算法框架,第一个顶点入队,while( !empty(queue)核心算法;(即
19、顶点出队,被访问,入队的过程) ,图的遍历 广优,(5)核心算法,1、从队列中取出一个顶点,2、逐个访问该顶点的邻接顶点,3、未被访问过的邻接顶点依次入队,4、当顶点的所有邻接顶点都访问过后,回到1,v = dequeue( queue );,p = graph.node_listv;,adjp = p-next_adj;,while(adjp != NULL)adjp = adjp-next_adj;,if( visitedadjp-node_index != true)v = adjp-node_index;visit( v ), visited v = true;inqueue(queu
20、e, v);,initialize visited , create_queue(queue);visit( v ), visited v = true;inqueue(queue, v );,while(!empty(queue),while( adjp != NULL)if( visited adjp-node_index != true)v = adjp-node_index;visit(v), visitedv = true;inqueue( queu, v); adjp = adjp-next;,void bfs(graph , v ),v = dequeue( queue); p
21、= graph-node_list v ; adjp = p-next_adj;,生成树与图,4.4 生成树与最短路径 4.4.1 生成树 假设存在这样一颗树: 树结点的集合与图的顶点集合相等 树的分支全部由图的边组成。称这颗树是这幅图的生成树 生成树是图的: 子集/子图 是一个不包含回路的子图 图的生成树是,2,1,4,3,不唯一的!,唯一的!,权值,生成树与图,4.4.2 最小费用生成树 在图所有生成树中,边的权值总和最小的生成树,6,5,3,6,6,4,2,4,1,6,边,13,1,46,2,25,3,14,4,36,4,23,5,12,6,35,6,56,6,将边按权值大小排列,生成树
22、与图,算法分析关键技术: 为什么在(1,4)边选中后不能选(3,6)边回路 在选择(3,6)边和(2,3)边时有什么不同,怎样设置条件以区别? 当时3和6位于同一图中,2、5和3位于不同的图中,6,5,3,6,6,4,2,4,1,6,14,4,36,4,23,5,生成树与图,1)开始时所有的节点都位于不同的图中,2)选择一条连接两个不同子图的最短边,3)连接以后两个子图的所有顶点都要改为在一个子图中,4)当所有的顶点都位于一个子图中,算法结束。,权值,边,13,1,46,2,25,3,14,4,36,4,23,5,12,6,思考,既然是颗树,如果选择边数为n1个时可不可以结束算法呢? 算法的优
23、化,图的最短路径,4.4.3 最短路径 图中两个顶点之间有 条路径 最短路径: 是路径中边(弧)的权值总和最小的路径 计算机网络中常使用最短路径算法计算最佳路径 交通网络中使用最短路径算法计算最短旅途,多,Dijkstra Algorithm,Floyd Algorithm,A1,A2,A4,A3,A5,2,6,5,1,2,1,5,1)初始化时,设A1到其它不直连顶点距离为,寻找A1到所有节点的最短路径,A2,A3,A4,A5,顶点,距离,路径,2)选择距离最短的路径,3)观察通过新选择的路径是否能更短地到达其它顶点,4)选择出的最短路径将不参加下一轮比较,5)反复2 - 4步,直到不剩有顶点
24、,2,6,5,A2,A3,A4,A1 A2 A33,更新,A1 A2 A3,3,A1 A2 A4 ,A1 A2 A5 4,A1 A2 A3 A44,更新,A1 A2 A3 A4,4,A1 A2 A3 A58,A1 A2 A5,4,A1 A2 A3 A4 A5,更新,Dijkstra Algorithm,算法分析:,1)需要路径的集合,(从A到所有其它顶点的路径)path i 当找到一条到该顶点的最短路径时就将该顶点从集合中移出s 顶点集合,2)需要最短路径的集合,找出的最短路径逐个放到集合中shortest_path i ,3)需要使用邻接矩阵存储的图A i j ,算法描述:,4)输出最短路径
25、的集合 shortest_path,1)初始化path,根据A v i ( i 顶点集合,且i v),2)每次从path中选择最短的一条,3)将该路径从path中取出并放在shortest_path中,4)比较并更改由该路径形成的新的较短路径,回到步骤2,x find_shortest_path(path);,short_path i = path x ;,if( path x + A x i path ji path i = path x + (x , i );,path_type * shortestpath_DIJ ( A , V ) ,v为指定的源点,for( i = 1 ; i =
26、VTXNUM ; i +),if( i = v ) continue;,else path i = (v , i ) ; ,if( A v i = 0 )path i = MAXCOST;,initialize_path( path );,for ( n = 1 ; n = VTXNUM ; n + ),x = find_shortest_path( path );,shortest_path x = path x ;,s = s + x ;,for( i = 1; i = VTXNUM; i +),if( NOT i IN s & x + A x i path i ),path i = path x + (x , i);,return shortest_path;,s = s + v ;,这些赋值语句和加法不是通常语言中的含义,表示将(x,i)这条边加入到v到x的路径path x 中,就形成了从v到x再到i的更短路径,作业,教材72页,第23、24题,