1、1,第6章 图,6.1 图的基本概念 6.2 图的存储结构 6.3 图的遍历 6.4 无向图的应用 6.5 有向图的应用 6.6 最短路径,2,第6章 图,6.1 图的基本概念 6.2 图的存储结构 6.3 图的遍历 6.4 无向图的应用 6.5 有向图的应用 6.6 最短路径,3,图的定义 图是由顶点集合(Vertex)及顶点间的关系集合组成的一种数据结构:G(V, E) 其中: V = x | x 某个数据对象是顶点的有穷非空集合; E = (x, y) | x, y V 是边(Edge)的集合或者E = | x, y V & Path是弧 (Arc)的集合,Path表示从顶点 x 到 y
2、 的一条单向通路,它是有方向的。,6.1 图的基本概念,4,无向图无向图G是由集合V(G)和E(G)组成。 其中:V(G)是顶点的非空有限集;E(G)是边的有限集合,边是顶点的无序对,记为(v,w)或(w,v),并且(v,w)=(w,v)。 有向图有向图G是由集合V(G)和E(G)组成。 其中:V(G)是顶点的非空有限集;E(G)是有向边(也称弧)的有限集合,弧是顶点的有序对,记为,v为起点,w为终点,并且。,6.1 图的基本概念,5,例2:有向图G2 V(G2)=1, 2, 3, 4, 5, 6 E(G2)=, , , , , ,例1:无向图G1 V(G1)=1, 2, 3, 4, 5, 6
3、, 7 E(G1)=(1,2), (1,3), (2,3), (2,4), (2,5), (5,6), (5,7),6.1 图的基本概念,6,相邻、邻接、关联: 如果e=(u, v)是无向图G中的一条边,则称u与v互为邻接点或相邻点;称边e与顶点u和v相关联。 如果a=是有向图G中的一条弧,则称u邻接到v或v邻接于u;称弧a与顶点u和v相关联。,6.1 图的基本概念,7,相邻、邻接、关联:,6.1 图的基本概念,与顶点1相邻(或邻接)的顶点有2、3、4; 边(2, 3)与顶点2和3相关联。,弧的顶点3邻接到顶点2,或者顶点2邻接于3; 弧与顶点3和2相关联。,8,权:边或弧上带有的相关数字,称
4、为权。 权的意义较为广泛,可以表示:距离、时间、费用等。 带权图(或网):带有权值的图,称为带权图(或网)。 无向带权图 有向带权图,6.1 图的基本概念,9,顶点的度(与树中结点的度不同): 在无向图G=(V, E)中,与顶点v所关联的边数,称为顶点v的度,记为D(v)。 在有向图G=(V, E)中,顶点v的度等于该顶点的入度与出度之和。 入度:以顶点v为终点的弧的数目,记为ID(v) 出度:以顶点v为起点的弧的数目,记为OD(v) 顶点v的度:D(v)=ID(v)+OD(v),6.1 图的基本概念,10,自环:称边(v, v)E(G)或弧E(G)为自环。 多重边(或弧):如果图中有两条或两
5、条以上相同的边(或弧),则称它们为多重边(或弧)。,6.1 图的基本概念,11,简单图:不含自环和多重边(或弧)的图,称为简单图;反之,称为非简单图。,6.1 图的基本概念,12,本章只讨论简单图,以下两类图不在本章的讨论之列:,6.1 图的基本概念,13,无向完全图:所有顶点间均有边的简单无向图称为无向完全图,即:n个顶点,有n(n-1)/2条边的简单无向图。,6.1 图的基本概念,14,有向完全图:所有顶点间均有两条方向不同弧的简单有向图称为有向完全图,即:n个顶点,有n(n-1)条弧的简单有向图。,6.1 图的基本概念,15,稀疏图(Sparse graph):边或弧的数目很少的图称为稀
6、疏图。 通常,n个顶点的图,其边或者弧的数目远小于nlog2n。,6.1 图的基本概念,稠密图(Dense graph):边或弧的数目较多的图称为稠密图。 通常,n个顶点的无向图,其边数接近n(n-1)/2。 通常,n个顶点的有向图,其弧数接近n(n-1)。,16,路径:在图G=(V, E)中,有一个顶点序列(Vi1, Vi2, , Vim),满足(Vij,Vij+1)E或 E,(1jm-1),则称此序列为从顶点Vi1到顶点Vim的一条路径。 其中:Vi1称为起点,Vim称为终点。 回路:起点和终点相同的路径,称为回路(或者圈)。,6.1 图的基本概念,17,简单路径:若一条路径上各顶点V1,
7、 V2, , Vm均不相同,则称这样的路径为简单路径。 简单回路:起点和终点相同的简单路径,称为简单回路(或者简单圈)。,6.1 图的基本概念,路径长度:沿路径边的数目或沿路径各边权值之和。,18,6.1 图的基本概念,19,子图:已知图G(V, E)和图G(V, E),若满足:VV且EE,则称图G为图G的子图。,6.1 图的基本概念,20,连通图:在无向图中,若从顶点v1到顶点v2有路径,则称顶点v1与v2是连通的。如果图中任意一对顶点都是连通的,则称此图是连通图。 连通分量:无向图中的极大连通子图称为该无向图的连通分量。,6.1 图的基本概念,21,强连通图:在有向图中,如果任意一对顶点v
8、i和vj,都存在一条从vi到vj和从vj到vi的有向路径,则称此有向图是强连通图。 强连通分量:有向图中的极大强连通子图称为该有向图的强连通分量。,强连通图,非强连通图G,非强连通图G有2个强连通分量,6.1 图的基本概念,22,生成树:一个连通图的极小连通子图称为该连通图的生成树。 如果连通图中有n个顶点,则它的生成树中将有n-1条边。,6.1 图的基本概念,23,生成森林:在非连通图中,每个连通分量可生成一棵树,所有连通分量生成的树可组成生成森林。,6.1 图的基本概念,24,图的基本操作: 对顶点的操作 定位、访问、插入、删除 获得指定顶点的邻接点 对边(或弧)的操作 插入、删除 对图的
9、操作 创建、删除、遍历(深度优先、广度优先),6.1 图的基本概念,25,图的抽象数据类型的定义class Graph public:Graph ( );void InsertVertex ( Type ,26,第6章 图,6.1 图的基本概念 6.2 图的存储结构 6.3 图的遍历 6.4 无向图的应用 6.5 有向图的应用 6.6 最短路径,27,6.2 图的存储结构,图有以下几种常用的存储结构: 邻接矩阵表示法:无向图、有向图 邻接表表示法:无向图、有向图 邻接多重表表示法:无向图 十字链表表示法:有向图,28,邻接矩阵(Adjacency Matrix)是一个表示图中各个顶点之间关系的
10、矩阵。 设G=(V, E)是一个含有n个顶点的图,它的邻接矩阵是一个二维数组G.edgenn,定义如下:,6.2.1 邻接矩阵表示法,29,6.2.1 邻接矩阵表示法,30,邻接矩阵的特点: 无向图的邻接矩阵一定是对称的; 有向图的邻接矩阵不一定是对称的。 从邻接矩阵中可以得到的信息: 在无向图的邻接矩阵中,统计第i行(或者列)中1的个数可得顶点i的度。 在有向图的邻接矩阵中,统计第i行中1的个数可得顶点i的出度,统计第i列中1的个数可得顶点i的入度。,6.2.1 邻接矩阵表示法,31,邻接矩阵也可以表示带权图,矩阵定义如下:,6.2.1 邻接矩阵表示法,32,邻接矩阵表示法图的定义,clas
11、s AdjMatrix /非带权图int n; /顶点个数int matrixMaxSize MaxSize; /邻接矩阵public:AdjMatrix (int m) n=m; /其他成员函数 ; /AdjMatrix class AdjMatrix /带权图const int INFINITE=;int n; /顶点个数float matrixMaxSizeMaxSize; /邻接矩阵public:AdjMatrix (int m) n=m;,33,void CreateGraph( ); /建立图G的邻接矩阵void LocateVex(v); /定位顶点v在图G中的位置void Ge
12、tVex(v); /得到图G中顶点v的值void FirstAdjVex(v); /确定图G中顶点v的第一个邻接点void NextAdjVex(v,w); /确定图G中顶点v相对于w的下一个邻接点void DFSTraverse(int v); /从顶点v开始对图进行深度优先遍历void BFSTraverse(int v); /从顶点v开始对图进行广度优先遍历 ; /AdjGraph,邻接矩阵表示法图的定义,34,邻接矩阵表示法的缺点: 存储单元个数仅与图中顶点个数有关,而与图中边(或弧)的数目无关。 n个顶点的图,其邻接矩阵中元素个数为n2,即:空间复杂度至少为O(n2)。 当图中顶点个
13、数较多,而边(或弧)的数目较少时,邻接矩阵中将有很多零元素,浪费空间。 采用“邻接表表示法”可提高存储空间的利用效率。,6.2.1 邻接矩阵表示法,35,邻接表(Adjacency List)表示法:存储单元个数不仅与图中顶点个数有关,还与图中边(或弧)的数目有关。 无向图的邻接表 有向图的邻接表,6.2.2 邻接表表示法,36,无向图的邻接表:为图中每个顶点建立一个单链表,其第i个单链表表示与顶点Vi相关联的所有边。 无向图的邻接表中有两类结点: 边结点:单链表中的结点。 表头结点:每个单链表的头结点。,6.2.2 邻接表表示法,37,无向图的邻接表:为图中每个顶点建立一个单链表,其第i个单
14、链表表示与顶点Vi相关联的所有边。 边结点的结构: 邻接点域(adjvex):与顶点Vi相邻接的顶点序号; 指针域(link):指向下一条与顶点Vi相关联的边。,6.2.2 邻接表表示法,38,无向图的邻接表:为图中每个顶点建立一个单链表,其第i个单链表表示与顶点Vi相关联的所有边。 表头结点的结构: 数据域(data):与该顶点有关的数据。 指针域(firstout):指向与该顶点相关联的第一条边。,6.2.2 邻接表表示法,39,6.2.2 邻接表表示法,例:无向图的邻接表。,40,有向图的邻接表:为图中每个顶点建立一个单链表,其第i个单链表链接了所有以顶点Vi为起点的弧。 有向图邻接表的
15、结构与无向图邻接表的结构一致: 弧结点:单链表中的结点。 表头结点:每个单链表的头结点。,6.2.2 邻接表表示法,41,例:有向图的邻接表。,6.2.2 邻接表表示法,存在问题:在有向图的邻接表中,不易找到指向某个顶点的弧。,42,例:有向图的逆邻接表(其第i个单链表链接了所有以顶点Vi为终点的弧)。,6.2.2 邻接表表示法,在有向图的逆邻接表中,很容易找到指向某个顶点的弧。,43,邻接表表示法存储空间分析: 设有n个顶点e条边的无向图,用邻接表表示该无向图时,需要n个表头结点和2e个边结点。 无向图邻接表所需存储空间:n+2e 设有n个顶点e条弧的有向图,用邻接表表示该有向图时,若不考虑
16、逆邻接表,需要n个表头结点和e个弧结点。 有向图邻接表所需存储空间:n+e,6.2.2 邻接表表示法,44,class EArcNode /边(或弧)结点int adjvex; /该边(或弧)所指向的顶点的位置EArcNode *link; /指向下一条边(或弧)的指针public:EArcNode(int adj) adjvex=adj; link=NULL; friend class LinkGraph; ; /EArcNode class VNode /表头结点DataType data; /顶点的信息EArcNode *firstout; /指向与该顶点所关联的第一条边或弧的指针pub
17、lic:VNode(DataType e) data=e; firstout=NULL; friend class LinkGraph; ; /VNode,邻接表表示法图的定义,45,class LinkGraph /邻接表的定义int n; /顶点的个数VNode gheadMaxSize; /邻接表public:LinkGraph(int m) n=m; void CreateGraph(); /建立图Gvoid LocateVex(int v); /得到顶点v在图中的位置 void GetVex(int v); /得到顶点v的值void FirstAdjVex(int v); /得到顶点
18、v的第一个邻接点void NextAdjVex(int v, int w);/确定图G中顶点出v相对于w的下一个邻接点void DFSTraverse(int v);/从顶点v开始,对图进行深度优先遍历void BFSTraverse(int v); /从顶点v开始,对图进行广度优先遍历 ; /LinkGraph,46,int FirstAdjVex(int v) if(gheadv-firstout) /书上没有此行代码return gheadv-firstout.adjvex;else return -1; /书上没有此行代码 /FirstAdjVex,int NextAdjVex(int
19、 v,int w) p=gheadv-firstout;while (p /NextAdjVex,图在邻接表上部分操作函数的实现:,47,邻接多重表表示法 该表示法只适合存储无向图。 存在的问题:在无向图的邻接表表示法中,每条边都被使用两次(被存放到两个链表中),因此如果想从邻接表中删除一条边,则需要对两个链表进行操作。 邻接多重表表示法:图中的每一条边只对应一个边结点,每个顶点仍只对应一个表头结点。,6.2.3 邻接多重表表示法,48,每个边结点包含五个域:mark是标志域,标记该边是否被遍历过; vex1与vex2是该边所关联的两个顶点在图中的序号; link1是指向下一条与顶点vex1相
20、关联的边; link2是指向下一条与顶点vex2相关联的边。,6.2.3 邻接多重表表示法,49,每个表头结点仍包含两个域:data存放该顶点的相关信息(如:顶点在图中的编号); firstEdge是指向第一条与该顶点相关联的边。,6.2.3 邻接多重表表示法,50,例:无向图的邻接多重表。,6.2.3 邻接多重表表示法,51,十字链表表示法 该表示法只适合存储有向图。 它是将有向图的邻接表和逆邻接表相结合后得到的一种链表。 十字链表表示法:图中的每一条弧对应一个弧结点,每个顶点对应一个表头结点。,6.2.4 十字链表表示法,52,每个弧结点包含五个域:mark是标志域,标记该边是否被遍历过;
21、 hvex表示该弧的起点在图中的位置(序号); tvex表示该弧的终点在图中的位置(序号); hink是指向起点相同的下一条弧; tink是指向终点相同的下一条弧。,6.2.4 十字链表表示法,53,每个表头结点包含三个域:data存放该顶点的相关信息(如:顶点在图中的编号); firstin是指向该顶点为终点的第一个弧结点; firstout是指向该顶点为起点的第一个弧结点。,6.2.4 十字链表表示法,54,例:有向图的十字链表。,6.2.4 十字链表表示法,55,第6章 图,6.1 图的基本概念 6.2 图的存储结构 6.3 图的遍历 6.4 无向图的应用 6.5 有向图的应用 6.6
22、最短路径,56,6.3 图的遍历,图的遍历:从图中某一顶点出发,按照某种搜索路径访问图中的所有顶点,使得每个顶点被访问且仅被访问一次。 图的两种遍历方法: 深度优先遍历(Depth-First Search, DFS) 广度优先遍历(Breadth-First Search, BFS) 两种遍历方法对无向图和有向图都适用。,57,6.3.1 深度优先遍历,深度优先遍历的基本思想: 首先,从图中某一顶点v开始,先访问v; 其次,从顶点v出发,访问它的第一个邻接点w1; 然后,再从顶点w1出发,访问与w1邻接但还未被访问过的顶点w2;,58,6.3.1 深度优先遍历,深度优先遍历的基本思想: 重复
23、上述过程,直到某一顶点的所有邻接点都被访问过,则退回到上一步访问过的顶点,并查看该顶点是否还有未被访问过的邻接点。 如果有,则访问其邻接点,之后再进行与前述类似的访问; 如果没有,则再往上退一步进行查看。 直到图中所有顶点都被访问完为止。,59,例:以顶点A为起点进行深度优先遍历。,DFS序列:ABEGCFDHI,6.3.1 深度优先遍历,由于没有规定 访问邻接点的顺序, 因此DFS序列可能不唯一。,60,为了保证图中每个顶点只被访问一次,需要设置一个辅助数组visitedn(n是图中顶点的个数): 数组元素的初值均为0(False),表示对应顶点未被访问过; 在图的遍历过程中,一旦某一个顶点
24、Vi被访问,就把其对应的数组元素visitedi置为1(True)。,6.3.1 深度优先遍历,61,图的深度优先遍历算法如下:,void DFSTraverse( ) /深度优先遍历算法int visitedn; /开辟访问标志数组for (v=0; vn; v+) visitedv=0; /初始化访问标志数组for (v=0; vn; v+)if (!visitedv)DFS(v); /每次从尚未访问过的顶点中/选取一个顶点v,从顶点v出发调用DFS(v) /DFSTraverse,6.3.1 深度优先遍历,62,void DFS(int v) /从顶点v出发访问包含该顶点v的最大连通子图
25、中的所有顶点visitedv=1;visit(v); /或coutv;for(w=FirstAdjVex(v); w!=-1; w=NextAdjVex(v, w)if (!visitedw)DFS(w); /递归调用 /DFS,图的深度优先遍历算法如下:,6.3.1 深度优先遍历,63,假定以邻接矩阵存储图: int FirstAdjVex(int v) int w=0;while(wn /没有未被访问过的邻接点 ,算法的时间复杂度为:O(n),6.3.1 深度优先遍历,64,假定以邻接矩阵存储图: int NextAdjVex(int v, int w) u=w+1;while(un /没
26、有未被访问过的邻接点 ,算法的时间复杂度为:O(n),6.3.1 深度优先遍历,65,假定以邻接表存储图:,int FirstAdjVex(int v) if(gheadv-firstout) return gheadv-firstout.adjvex;elsereturn -1; ,算法的时间复杂度为:O(1),6.3.1 深度优先遍历,66,int NextAdjVex(int v, int w) p=gheadv-firstout;while (p ,假定以邻接表存储图:,6.3.1 深度优先遍历,算法的时间复杂度为:O(D(v),67,深度优先算法的时间复杂度: 设有n个顶点,e条边(
27、或弧)的无(或有)向图。 如果用邻接矩阵存储图:(邻接矩阵与e无关) 在DFSTraverse( )函数中,初始化visited数组所需时间为O(n),并调用n次DFS( )函数; 在DFS( )函数中,需要遍历邻接矩阵的一行才能得到邻接点,因此循环将执行n次; 综上,无向图深度优先遍历算法的时间复杂度为O(n)+O(n*n)=O(n+n2),即:O(n2)。 有向图的深度优先遍历算法的时间复杂度也为O(n2)。,6.3.1 深度优先遍历,68,深度优先算法的时间复杂度: 设有n个顶点,e条边(或弧)的无(或有)向图。 如果用邻接表存储图: 在DFSTraverse( )函数中,初始化visi
28、ted数组所需时间为O(n),并调用n次DFS( )函数; 在DFS( )函数中,每个顶点对应链表中边结点的个数为该顶点的度,因此循环将执行D(V)次。 综上,无向图深度优先遍历算法的时间复杂度为O(n)+O(2e)=O(n+2e),即:O(n+2e)。 有向图的深度优先遍历算法的时间复杂度为O(n+e)。(只有出度邻接表的情况),6.3.1 深度优先遍历,69,练习1,A,C,E,G,B,D,练习2,6.3.1 深度优先遍历,以A为起始点,完成图的深度优先遍历。,70,6.3.2 广度优先遍历,广度优先遍历的基本思想: 首先,从图中某一顶点v开始,先访问v; 其次,从顶点v出发,依次访问它的
29、所有未被访问过的邻接点w1, w2, , wt; 然后,再顺序访问w1, w2, , wt的所有未被访问过的邻接点; 如此上述过程,直到图中所有顶点都被访问完为止。,71,6.3.2 广度优先遍历,例:以顶点A为起点进行广度优先遍历。,由于没有规定 访问邻接点的顺序, 因此BFS序列可能不唯一。,BFS序列:ABCDEFGHI,72,广度优先遍历是一种逐层搜索过程,每向前走一步可能访问一批顶点,不存在深度优先遍历中回退的情况。 因此,广度优先遍历不是一个递归过程,其算法也不是递归的。,6.3.2 广度优先遍历,73,为了实现逐层访问,算法中需要使用一个队列,以保存正在访问的顶点,同时可以方便向
30、下一层访问。 与深度优先遍历过程一样,为避免顶点被重复访问,需要一个辅助数组visitedn,对被访问过的顶点加以标记。,6.3.2 广度优先遍历,74,广度优先遍历(BFS)的算法思想: (1)清空队列Q; (2)对出发顶点v做如下操作:v入队,并标记v; (3)当队列Q不空反复做:(3.1)出队头元素到u;访问u; (3.2)将u的未被访问的邻接点w入队;标记w。,6.3.2 广度优先遍历,75,void BFSTraverse( ) /广度优先遍历算法int visitedn; /设置访问标志数组for (v=0; vn; v+) visitedv=0; /初始化访问标志for (v=0
31、; vn; v+) if (!visitedv) BFS(v); /BFSTraverse,图的广度优先遍历算法如下:,6.3.2 广度优先遍历,76,void BFS(int v) Q=new Queue( ); /清空队列QQ.Enter(v); /将起始顶点v入队visitedv=1; /标记vwhile (!Q.IsEmpty() /队列Q不空u=Q.Leave( ); /出队头元素到uvisited(u); /访问ufor (w=FirstAdjVex(u); w!=-1; w=NextAdjVex(u, w)if (!visitedw) Q.Enter(w); /将u的每个未被访问
32、的邻接点w入队visitedw=1; /BFS,图的广度优先遍历算法如下:,77,广度优先遍历的算法分析与深度优先遍历算法相同,区别在于访问顶点的顺序不同。 采用邻接矩阵存储图时,算法的时间复杂度为O(n2)。(无向图和有向图相同) 采用邻接表存储图时,算法的时间复杂度为O(n+2e)。(有向图的时间复杂度为O(n+e)),6.3.2 广度优先遍历,78,练习1,A,C,E,G,B,D,练习2,6.3.2 广度优先遍历,以A为起始点,完成图的广度优先遍历。,79,6.3 图的遍历,图的遍历小结: 对于连通图,则从图中某一个顶点出发可以访问到图中所有顶点; 对于非连通图,则需分多次从图中的多个顶
33、点出发按连通分量依次进行遍历。,80,第6章 图,6.1 图的基本概念 6.2 图的存储结构 6.3 图的遍历 6.4 无向图的应用 6.5 有向图的应用 6.6 最短路径,81,6.4 无向图的应用,问题:假设要在n个城市之间建立通信联络网,连通n个城市只需要修建n-1条线路即可,如何在最节省经费的前提下建立这个通信网? 这是一个关于无向图的应用问题。,82,问题分析: 由于在每两个城市之间都可以建立一条通信线路,n个城市之间最多可以设置n(n-1)/2条线路; 假设建立每条线路都要付出一定的经济代价,因此,如何从中选择n-1条线路使总的耗费最少成为问题的关键。,6.4 无向图的应用,83,
34、问题等价: 可用带权图来描述这个问题: 顶点:表示城市; 边:表示城市之间的通信线路; 边的权值:表示相应的经济代价。,6.4 无向图的应用,84,问题等价: 因此,建立总花费最少的通信网问题可被转换为求带权图的最小生成树问题。 什么是带权图的最小生成树?,6.4 无向图的应用,85,最小生成树(Minimum Cost Spanning Tree) 按照生成树的定义,含n个顶点的带权图的生成树将有n个顶点、n-1条边。 因此,由同一个带权图可能构建出许多不同的生成树。,6.4 无向图的应用,86,最小生成树(Minimum Cost Spanning Tree) 在众多生成树中,代价之和最小
35、的那棵生成树被称为最小代价生成树(简称最小生成树)。,6.4 无向图的应用,87,最小生成树(Minimum Cost Spanning Tree) 在众多生成树中,代价之和最小的那棵生成树被称为最小代价生成树(简称最小生成树)。 问题:根据一个带权图,如何有效的构建其相应的最小生成树? 简单方法:把所有生成树都构建出来,从中选出代价最小的那棵效率太低。 两种常用方法: Prim(普里姆)算法 Kruskal(克鲁斯卡尔)算法,6.4 无向图的应用,88,Prim(普里姆)算法,Prim算法的基本思想: 设T和U分别表示连通带权图G=V, E最小生成树中边的集合和顶点集合,初始都为空。 依次执
36、行以下步骤: (1) 选取任意一个顶点u加入到集合U中; (2) 在所有uU,vV-U的边 (u, v)E中找一条权值最小的边(u, v)加入到T中,同时顶点v也加入到U中; (3) 重复步骤(2),直到U=V为止。,1,Prim算法构造最小生成树示例(从1号顶点开始),90,Prim(普里姆)算法,Prim算法的实现: 采用邻接矩阵存储连通无向网。 在构建生成树过程中,需要设置一个辅助数组closedgen,每个元素有两个域:lowcost:存放生成树U集合内顶点到生成树之外各顶点V-U的各边上的当前最小权值; adjvex:记录生成树之外各顶点V-U距离U内的哪个顶点最近(即:权值最小)。
37、,91,Prim算法构造最小生成树:,class AddArray /辅助数组类int adjvex;float lowcost; ; void MinSpanTree_Prim(int u, AddArray closedge ,AddArray edge , AdjMatrix G) /以邻接矩阵存储图,从顶点u出发构造最小生成树,/并将结果保存到数组edge中。 int Un; /最小生成树的顶点(标志)数组for(v=0; vn; v+)Uv=0; /初始化最小生成树的顶点(标志)数组Uu=1; /从顶点u开始,92,for (v=0; vn; v+) /初始化辅助数组closedge
38、 if (u!=v) closedgev.adjvex=u;closedgev.lowcost=G.matrixuv;for (t=1; tn; t+) /选择其余的n-1个顶点k=minimum(closedge); /求出下一个顶点ku=closedgek.adjvex; edgek.lowcost=G.matrixku;/保存选中边的权值edgek.adjvex=u; /保存选中边的顶点Uk=1;,Prim算法构造最小生成树:,93,for (j=0; jn; j+) /更新辅助数组closedge if (Uj=0 /MinSpanTree_Prim,Prim算法构造最小生成树:,94
39、,A,C,F,D,B,E,BE 3,BE 3,E,A,C,F,D,B,CB 5,CE 6,CB 5,B,E,A,C,F,D,FD 2,CE 6,FD 2,CB 5,B,D,E,A,C,F,CF 4,CF 4,CE 6,AD 5,CB 5,B,D,E,F,A,C,AC 1, , ,AD 5,AC 1,AB 6,B,C,D,E,F,A,edge,F,E,D,C,B,V-U,U,A,B,C,D,E,F,1,5,3,4,2,Prim(普里姆)算法,从顶点A出发,利用Prim算法构造最小生成树过程中辅助数组值的变化。,95,对于n个顶点连通无向网,Prim算法的时间复杂度为O(n2)。 Prim算法的时
40、间复杂度与边的数目无关。 Prim算法适用于边稠密的网络。 注意事项:当候选边有相同权值时,由于选择的随意性,使得最小生成树的结构可能不唯一。,Prim(普里姆)算法,96,课堂练习:求出如下连通无向网的最小生成树。,最小生成树 不唯一!,97,Kruskal(克鲁斯卡尔)算法,Kruskal算法的基本思想: 设一个含有n个顶点的连通无向网N = V, E ,最初先构造一个只有n个顶点,没有边的非连通图T = V, ,图中每个顶点自成一个连通分量。 当在E中选到一条具有最小权值的边时,若该边的两个顶点落在不同的连通分量上,则将此边加入到T中;否则,将此边舍去,重新选择一条权值最小的边。 按上述
41、方式重复下去,直到所有顶点都在同一个连通分量上为止。,98,例:Kruskal算法构造最小生成树。,Kruskal(克鲁斯卡尔)算法,99,Kruskal(克鲁斯卡尔)算法,Kruskal算法的时间复杂度为O(elog2e)。 Kruskal算法的时间复杂度仅与边的数目有关,而与顶点的数目无关。 Kruskal算法适用求边稀疏的连通无向网的最小生成树。,100,练习:采用Kruskal算法求如下连通无向网的最小生成树。,Kruskal(克鲁斯卡尔)算法,101,第6章 图,6.1 图的基本概念 6.2 图的存储结构 6.3 图的遍历 6.4 无向图的应用 6.5 有向图的应用 6.6 最短路径
42、,102,有向图的两类应用问题: 拓扑排序 关键路径,6.5 有向图的应用,103,有向图的两类应用问题: 拓扑排序 关键路径,6.5 有向图的应用,104,活动网络(Activity Network) 在实际中,计划、施工过程、生产流程、程序流程等都是“工程”。 除了很小的工程之外,一般都把工程分为若干个叫做“活动”的子工程。完成了这些活动(即:子工程),这个工程就完成了。,6.5.1 拓扑排序,105,活动网络(Activity Network) 可以用有向图来表示一个工程。 顶点:表示活动; 弧:表示活动Vi必须先于活动Vj进行。 这种有向图被称为顶点表示活动的网络(Activity O
43、n Vertices, AOV),也称活动网络。,6.5.1 拓扑排序,106,活动网络(Activity Network) 例如:计算机专业学生攻读学位的过程就是一个工程。 每一门课程的学习是整个工程中的一个活动。 有些课程要求有先修课程,有些则不要求。 这样,课程之间可能存在先后关系。,6.5.1 拓扑排序,107,6.5.1 拓扑排序,例:AOV网络表示各门课程之间先后关系。,108,拓扑有序序列:在AOV网络中,若将各个顶点 (代表各个活动)排列成一个线性有序的序列v1, v2, , vn,使得若从顶点vi到vj有一条有向路径,则在序列中顶点vi必须排在顶点vj之前。 拓扑排序:在AO
44、V网络中,寻找一个拓扑有序序列的过程。,6.5.1 拓扑排序,109,例:对如下AOV网络进行拓扑排序,可得到拓扑有序序列:,C1 , C2 , C3 , C4 , C5 , C6 , C8 , C9 , C7 或 C1 , C8 , C9 , C2 , C5 , C3 , C4 , C7 , C6,拓扑序列并不唯一,6.5.1 拓扑排序,110,B,D,A,C,例:对如下AOV网络,无法求得它的拓扑有序序列。,因为该AOV网络中存在一个有向回路B, C, D。,6.5.1 拓扑排序,111,在AOV网络中不能出现有向回路。 如果出现了有向回路,则意味着某项活动将以自己作为先决条件或者活动之间
45、互为条件。 如果AOV网络中存在有向回路,则此AOV网络所代表的工程是不可行的。 因此,对给定的AOV网络,必须先判断它是否存在有向回路。,6.5.1 拓扑排序,112,检测AOV网络是否存在有向回路的方法:对AOV网络进行拓扑排序,查看能否得到拓扑有序序列。 如果至少存在一个拓扑有序序列,则该AOV网络中一定不存在有向回路。 反之,该AOV网络一定存在有向回路,因此,该AOV网络无法表示一个工程。,6.5.1 拓扑排序,113,拓扑排序算法的基本思想: 输入AOV网络。令n为顶点个数。 在AOV网络中选一个入度为零的顶点,并输出之; 从图中删去该顶点,同时删去所有以它为起点的弧; 重复以上、
46、步,直到 全部顶点均已输出(可获得拓扑有序序列,拓扑排序完成),AOV网络不存在有向回路; 或者图中还有未输出的顶点,但已无入度为零的顶点,AOV网络中必定存在有向回路。,6.5.1 拓扑排序,114,例:对如下AOV网络进行拓扑排序。,6.5.1 拓扑排序,115,例:对如下AOV网络进行拓扑排序。,6.5.1 拓扑排序,116,例:对如下AOV网络进行拓扑排序。,6.5.1 拓扑排序,117,例:对如下AOV网络进行拓扑排序。,6.5.1 拓扑排序,118,例:对如下AOV网络进行拓扑排序。,6.5.1 拓扑排序,119,例:对如下AOV网络进行拓扑排序。,6.5.1 拓扑排序,120,例
47、:对如下AOV网络进行拓扑排序。,6.5.1 拓扑排序,拓扑排序完成,最后得到的拓扑有序序列为C4 , C0 , C3 , C2 , C1 , C5 。,121,拓扑排序算法的实现: AOV网络的存储结构邻接表。 为了便于寻找入度为零的顶点,在表头结点中增加一个“入度域”,用于记录顶点的入度。,6.5.1 拓扑排序,122,例:AOV网络在拓扑排序过程中,邻接表存储结构示意图(链表中的结点以升序排列):,6.5.1 拓扑排序,123,拓扑排序算法的实现: AOV网络的存储结构邻接表。 为了便于寻找入度为零的顶点,在表头结点中增加一个“入度域”,用于记录顶点的入度。 在拓扑排序过程中,为了避免重复检测入度为零的顶点,需要建立一个栈,用于存放入度为零的顶点。,6.5.1 拓扑排序,124,采用栈结构实现拓扑排序算法: (1)查找邻接表中所有入度为0的顶点,将它们入栈。 (2)当栈非空时,反复做下列操作: 栈顶元素Vi出栈,并输出; 在邻接表中,查找Vi的所有后继顶点Vj,将顶点Vj的“入度域”减1,如果Vj入度域变为0,则顶点Vj入栈。 (3)当栈空时,若AOV网络输出的顶点个数为图的顶点数,则拓扑排序正常结束;否则,AOV网络中存在有向回路。,