540 likes | 672 Views
数据结构 第七章 图. 本章内容 7.1 图的基本概念 7.2 图的存储结构 7.3 图的遍历 7.4 图的连通性 7.5 有向无环图及其应用 7.6 最短路径. 7.1 图的基本概念. 图的定义 图:图 G 由两个集合构成,记作 G=<V,E>, 其中 V 是顶点 ( vertex ) 的非空有限集合, E 是边 ( edge ) 的有限集合,其中边是顶点的无序对或有序对集合。 邻接点:边的两个顶点 关联边:若边 e= (v, u), 则称顶点 v 、 u 关连边 e 顶点 V 的度:与顶点 V 相关联的边的数目。在有向图中:
E N D
本章内容 7.1图的基本概念 7.2图的存储结构 7.3图的遍历 7.4图的连通性 7.5有向无环图及其应用 7.6最短路径
7.1 图的基本概念 • 图的定义 • 图:图G由两个集合构成,记作G=<V,E>,其中V是顶点(vertex)的非空有限集合,E是边(edge)的有限集合,其中边是顶点的无序对或有序对集合。 • 邻接点:边的两个顶点 • 关联边:若边e= (v, u), 则称顶点v、u 关连边e • 顶点V的度:与顶点V相关联的边的数目。在有向图中: • 顶点V的出度(in degree )=以V为起点有向边数 • 顶点V的入度(out degree)=以V为终点有向边数 • 顶点V的度= V的出度+V的入度 • 设图G的顶点数为n,边数为e,则:图的所有顶点的度数和等于2*e,即等于每条边对图的所有顶点的度数之和。 7-3
V1 V0 V2 V3 V4 7.1 图的基本概念 • 例1:G1=<V1,E1>, V1={v0 ,v1,v2,v3,v4 }, E1={(v0,v1),(v0,v3),(v1,v2),(v1,v4),(v2,v3)(v2,v4)} 无序对(vi,vj):用连接顶点vi、vj的线段表示,称为无向边; 7-4
V1 V0 V2 V3 7.1 图的基本概念 • 例2:G2=<V2,E2>,V2={v0 v1,v2,v3} E2={<v0,v1 > , <v0,v2 >, <v2,v3 >,<v3,v0 >} 有序对<vi,vj> :用以为vi起点、以vj为终点的有向线段表示,称为有向边或弧; 7-5
V1 V0 V1 V0 V2 V2 V1 V0 V3 V4 V3 V4 V2 (a) (b) (c) V3 V4 7.1 图的基本概念 • 子图(subgraph) 图 G=(V,E),G’=(V’,E’)中,若 V’≤V,E’≤E,并且E’中的边所关联的顶点都在V’中,则称图G’是图G的子图 • 例3: 下列图b、图c 是图a 的子图。 7-6
V1 V0 V2 V3 V1 V0 V2 V3 V4 (a) (b) 7.1 图的基本概念 • 路径(path):在图G=(V,E)中,如果存在顶点序列Vp,Vi1,Vi2,…,Vin,Vq,使得 (Vp, Vi1),(Vi1, Vi2),…,(Vin, Vq)(若对有向图,则使得<Vp,Vi1>,<Vi1,Vi2> ,…,<Vin,Vq>)都在E中,则称从顶点Vp到顶点Vq存在一条路径,若p=q,则称为回路。 • 简单路径(simple path) :在一条路径中,除起点和终点外,所有顶点各不相同,则称为简单路径。由简单路径组成的回路称为简单回路(simple cycle)。类似地,还有有向无环图的概念(directed acyclic graph,DAG)。 • 例4:在下图a中,V0,V1,V2,V3是简单路径; V0,V1,V2,V4,V1不是简单路径;在图b中, V0,V2,V3,V0是简单回路。 7-7
V1 V1 V1 V0 V0 V0 V1 V0 V2 V2 V3 V4 V3 V4 V3 V4 V3 V4 连通图 非连通图 非强连通图 强连通图 7.1 图的基本概念 • 连通图:在无向图G=( V, E)中,若对任何两个顶点 v、u 都存在从v 到 u 的路径,则称G是连通图(connected graph); • 强连通图:在有向图G=< V, E >中,若对任何两个顶点 v、u 都存在从v 到 u 的路径,则称G是强连通图。 7-8
V1 V1 V0 V2 V2 V4 V3 V4 连通分图 连通分图 非连通图 7.1 图的基本概念 • 连通分图(强连通分量):无向图G 的极大连通子图称为G的连通分量。极大连通子图意思是:该子图是 G 连通子图,将G 的任何不在该子图中的顶点加入,子图不再连通。 • 例5:下例是图的连通分图和非连通分图的示例: 7-9
7.1 图的基本概念 • 有根的图:一个有向图中,若存在一个顶点V0,从此顶点有路径可以到达图中其它所有顶点,则称此有向图为有根的图,V0称作图的根。 • 网络:带权的连通图。 • 自由树(free tree):不带有简单回路的无向图,它是连通的,并且具有|V|-1条边。 7-10
V1 V0 V1 V0 V0 = V3 V1 V2 V2 V2 V4 V3 V4 V3 V4 连通图G1 连通图G1的生成树 7.1 图的基本概念 • 生成树:包含无向图G 所有顶点的的极小连通子图称为G 的生成树。 极小连通子图意思是:该子图是G 的连通子图,在该子图中删除任何一条边,子图不再连通。 • 生成树定义:若T是G 的生成树当且仅当T 满足如下条件: • T是G 的连通子图 • T包含G 的所有顶点 • T中无回路 7-11
class Graph{ //图的ADT public: int VerticesNum(); //返回图的顶点个数 int EdgesNum(); //返回图的边数 //返回与顶点oneVertex相关联的第一条边 Edge FirstEdge(int oneVertex); //返回与边PreEdge有相同顶点oneVertex的下一条边 Edge NextEdge(Edge preEdge); bool setEdge(int fromVertex, int toVertex, int weight ); //添加一条边 bool delEdge(int fromVertex, int toVertex);//删一条边 //如果oneEdge是边则返回TRUE,否则返回FALSE bool IsEdge(Edge oneEdge); //返回边oneEdge的始点 int FromVertex(Edge oneEdge); //返回边oneEdge的终点 int ToVertex(Edge oneEdge); //返回边oneEdge的权 int Weight(Edge oneEdge); }; 7.2 图的存储结构 • 图的抽象数据类型(C++) 7-12
V1 V0 0 1 0 1 0 1 0 1 0 1 0 1 0 1 1 1 0 1 0 0 0 1 1 0 0 V2 V3 V1 V0 0 1 1 0 0 0 0 0 0 0 0 1 1 0 0 0 V2 V3 V4 7.2 图的存储结构 6.3.1 图的相邻矩阵(adjacency matrix)表示法 • 相邻矩阵 • 表示顶点间相邻关系的矩阵。 • 若G是一个具有n个顶点的图,则G的相邻矩阵是如下定义的nn矩阵: A[i,j] = • 相邻矩阵的空间代价为O(n2) 1,若 (Vi, Vj) (或<Vi, Vj >) 是图G的边; 0,若 (Vi, Vj) (或<Vi, Vj>) 不是图G的边。 7-13
7.2 图的存储结构 • 邻接矩阵的C++实现 template <int max_size>class Graph { int count; //顶点数bool adjacency[max_size][max_size]; //邻接矩阵}; 7-14
7.2 图的存储结构 • 无向图数组表示法特点 • 无向图邻接矩阵是对称矩阵,同一条边表示了两次; • 顶点v的度:等于二维数组对应行(或列)中1的个数; • 判断两顶点v、u是否为邻接点:只需看组对应分量是否为1; • 顶点个数不变,在图中增删边时,只需对数组对应分量赋值1或清0; • 设图的顶点数为 n ,存储图用一维数组, 数组元素有 m 个 (m>=n),则 G占用存储空间:m+n2;G占用存储空间只与它的顶点数有关,与边数无关;适用于边稠密的图; 对有向图的数组表示法可做类似的讨论 7-15
7.2 图的存储结构 • 图的基本操作 • 求无向图某顶点 vi的度 (或有向图中vi的出度):A[i][0] 到A[i][n-1] 中的非0个数,即数组A中第i 行的非0 元素的个数; • 求有向图某顶点vi的 入度: A[0][i]到A [n-1][i] 中的非0个数,即数组A中第i 列的非0 元素的个数; • 检测图中的总边数:扫描整个数组A,统计出数组中非0元素的个数。无向图的总边数为非0元素个数的一半,而有向图的总弧数为非0元素个数; 7-16
顶点号 邻接 0 1 3 ^ 1 0 2 4 ^ V1 V0 2 1 3 4 ^ V2 3 0 2 ^ 4 1 2 ^ V3 V4 7.2 图的存储结构 6.3.2 图的邻接表(adjacency list)表示法 • 邻接表:是图的链式存储结构 • 无向图的邻接表 • 顶点:通常按编号顺序将顶点数据存储在一维数组中; • 关联同一顶点的边:用线性链表存储 7-17
7.2 图的存储结构 • 无向图的邻接表的特点 • 在G邻接表中,同一条边对应两个结点; • 顶点v的度:等于v对应线性链表的长度; • 判定顶点v ,u是否邻接:看v对应线性链表中有无对应的结点; • 在G中增减边:要在两个单链表插入、删除结点; • 设存储顶点的一维数组大小为m(m图的顶点数n), 图的边数为e,G占用存储空间为:m+2*e。G占用存储空间与 G 的顶点数、边数均有关;适用于边稀疏的图 7-18
V1 V0 起点号 邻接 0 1 2 ^ V2 V3 1 ^ 2 3 终点号 邻接 3 0 ^ 0 3 0 1 ^ 有向图的邻接表 ^ 有向图的逆邻接表 2 0 ^ 3 2 7.2 图的存储结构 • 有向图的邻接表 • 顶点:用一维数组存储(按编号顺序) • 以同一顶点为起点的弧:用线性链表存储 • 有向图的逆邻接表 • 用一维数组存储(按编号顺序) • 以同一顶点为终点的弧:用线性链表存储 7-19
7.2 图的存储结构 • 邻接多重表(adjacency multilist) • 在顶点表中设计两个指针 • 第一个指向以此顶点为始点的第一条边 • 第二个指向以此顶点为终点的第一条边 • 边表 • 第一个指针指向始点与本边始点相同的下一条边 • 第二个指针指向终点与本边终点相同的下一条边 • 故仅用表中第一个链便得到有向图的出边表,仅用第二个链便得到有向图的入边表 7-20
7.2 图的存储结构 邻接多重顶点表 • 顶点表中每个节点有两个指针: • 第一个指向以此顶点为始点的第一条边 • 第二个指向以此顶点为终点的第一条边 7-21
7.3 图的遍历 • 图的遍历 (graph traversal) 给出一个图G和其中任意一个顶点V0,从V0出发系统地访问G中所有的顶点,每个顶点访问一次,叫做图的遍历。 • 图的遍历的典型算法 • 从一个顶点出发,试探性访问其余顶点,同时必须考虑: • 从一顶点出发,可能到达不了所有其它顶点,如非连通图; • 也有可能会陷入死循环,如存在回路的图。 • 解决办法 • 为图的每个顶点保留一个标志位 (mark bit); • 算法开始时,所有顶点的标志位置零; • 在周游的过程中,当某个顶点被访问时,其标志位就被标记为已访问。 • 常用的图遍历方法 • 深度优先遍历 • 宽度优先遍历 7-22
7.3 图的遍历 • 图的遍历算法 void graph_traverse(Graph& G){ //对图所有顶点的标志位进行初始化 for(int i=0;i<G.VerticesNum();i++) G.Mark[i]=UNVISITED; //检查图的所有顶点是否被标记过,如果未被标记, //则从该未被标记的顶点开始继续周游 //do_traverse函数用深度优先或者广度优先 for(int i=0;i<G.VerticesNum();i++) if(G.Mark[i]== UNVISITED) do_traverse(G, i); } 7-23
7.3 图的遍历 7.3.1 深度优先搜索 • 深度优先搜索(depth-first search,简称DFS)原理 • 从图中某顶点v出发; • 访问顶点v; • 依次从v的未被访问的邻接点出发,继续按此法进行遍历; 7-24
V6 V2 V3 V1 V0 V5 V4 V7 V2 V0 V7 V6 V5 V4 V3 V1 7.3 图的遍历 例:求图G以V0起点的的深度优先序列: • V0,V1,V3,V7,V4,V2,V5,V6, 这是其中一种遍历 方案所经过的路径 • V0,V1,V4,V7,V3,V2,V5,V6 • 由于没有规定访问邻接点的顺序,深度优先遍历序列不唯一 • 如果将图顶点的邻接点看作二叉树结点的左、右孩子,深度优先遍历与先序遍历很类似 7-25
7.3 图的遍历 void DFS(Graph& G, int V) //深度优先搜索算法实现 { G.Mark[V]= VISITED; //访问顶点V,并标记其标志位 PreVisit(G, V);//访问V for(Edge e=G. FirstEdge(V); G.IsEdge(e); e=G. NextEdge(e)) //访问V邻接到的未被访问过的顶点,并递归地按照深度优先的方式进行遍历 if(G.Mark[G. ToVertices(e)]== UNVISITED) DFS(G, G. ToVertices(e)); PostVisit(G, V);//访问V } 7-26
7.3 图的遍历 • 深度优先搜索算法的时间复杂度 DFS对每一条边处理一次(无向图的每条边从两个方向处理),每个顶点访问一次。 • 采用邻接表表示时,有向图总代价为O(|V|+|E|),无向图为O(|V|+2|E|) • 采用相邻矩阵表示时,处理所有的边需要O(|V|2)的时间 ,所以总代价为O(|V|+|V|2)= O(|V|2)。 7-27
7.3 图的遍历 7.3.2深度优先搜索 • 广度优先搜索 (breadth-first search,简称BFS)原理 • 从未访问过的顶点vi出发: • 访问顶点vi ; • 访问 vi 的所有未被访问的邻接点w1 ,w2 , …wk ; • 依次从这些邻接点出发,访问它们的所有未被访问的邻接点; 依此类推,直到图中所有访问过的顶点的邻接点都被访问; 7-28
V5 V3 V1 V0 V4 V6 V2 V7 V1 V2 V0 V7 V6 V5 V4 V3 7.3 图的遍历 例:求图G以V0起点的的广度优先序列: • V0,V1,V2,V3,V4,V5,V6,V7 这是其中一种遍历 方案所经过的路径 • 由于没有规定访问邻接点的顺序,广度优先遍历序列不唯一 • 类似于树的按层遍历 • 在广度优先遍历算法中,需设置一队列Q,保存已访问的顶点,并控制遍历顶点的顺序。 7-29
7.3 图的遍历 • 广度优先搜索算法 void BFS(Graph& G, int V) { //初始化广度优先周游要用到的队列 using std::queue; queue<int> Q; //访问顶点V,并标记其标志位, V入队 G.Mark[V]= VISITED; Visit(G, V); Q.push(V); while(!Q.empty()) //如果队列仍然有元素 { int V=Q.front();//顶部元素 Q.pop();//出队 //将与该点相邻的每一个未访问点都入队 for(Edge e=G.FirstEdge(V); G.IsEdge(e);e=G.NextEdge(e)) { if(G.Mark[G.ToVertex(e)]== UNVISITED) {G.Mark[G.ToVertex(e)]=VISITED; Visit(G, G.ToVertex(e)); //入队 Q.push(G.ToVertex(e)); } } } } 7-30
7.3 图的遍历 • 广度优先搜索算法的时间复杂度 与深度优先搜索算法的时间复杂度相同 7-31
7.4 图的连通性 • 生成树: n 个顶点的连通网络的生成树有 n 个顶点、n-1 条边。生成树是连通图的极小连通子图。 • 由深度优先遍历得到的为深度优先生成树; • 由广度优先遍历得到的为广度优先生成树; • 一个连通图的生成树可能不唯一。 • 问题:对于给定的连通网络,如何求得其生成树? • 对含有n个顶点的连通图G,从任一顶点出发,作一次深度优先或广度优先遍历,将遍历过程中经过的n-1条边和图中的n个顶点连接起来构成一个极小连通子图,就是图G的一棵生成树。 • 用不同的遍历图的方法,可以得到不同的生成树;从不同的顶点出发,也可能得到不同的生成树。 7-32
7.4 图的连通性 • 最小生成树 生成树各边的权值总和称为生成树的权。权最小的生成树称为最小生成树。 • 最小生成树应用实例 • 假设要在n个城市之间建立通信网。令图G的顶点表示城市,边表示连接两个城市的通信线路,边的权值表示通信线路的长度或代价。在n个城市间构造通信网最少需要n-1条线路,问:如何选择这n-1条边,使得构造这个通信网总的代价最小? • 构造最小生成树的准则 • 必须只使用该网络中的边来构造最小生成树; • 必须使用且仅使用 n-1 条边来联结网络中的 n 个顶点; • 不能使用产生回路的边。 7-33
U V—U u v 7.4 图的连通性 • 最小生成树的构造算法 • MST 性质:设 G =(V,E)是一个连通网络,U 是顶点集 V 的一个真子集。若(u,v)是 G 中所有的一个顶点在 U,另一个顶点不在 U 的边中,具有最小权值的一条边,则一定存在 G 的一棵最小生成树包括此边。 7-34
7.4 图的连通性 • 证明(反证法): 假设 G 中任何一棵最小生成树都不包含(u,v)。 设T是一棵最小生成树但不包含(u,v)。由于T是最小生成树,所以 T 是连通的,因此有一条从u到v的路径,且该路径上必有一条连接两个顶点集 U、V-U 的边(u',v'),其中u'∈U,v'∈V-U。当把边(u,v)加入到T中后,得到一个含有边(u,v)的回路。删除边(u',v'),上述回路即被消除。由此得到另一棵生成树 T', T和T'的区别仅在于用边(u,v)代替了(u',v')。由于(u,v)的权<= (u',v')的权,所以, T'的权<= T的权,与假设矛盾。 7-35
7.4 图的连通性 • 普里姆(Prim)算法 • 基本思想: • 从连通网络 N = { V, E }中的某一顶点 u0出发,选择与它关联的具有最小权值的边(u0, v),将其顶点加入到生成树的顶点集合U中。 • 以后每一步从一个顶点在U中,而另一个顶点不在U中的各条边中选择权值最小的边(u, v),把它的顶点加入到集合U中。如此继续下去,直到网络中的所有顶点都加入到生成树顶点集合U中为止。 7-36
7.4 图的连通性 • 在构造过程中,需引入一个辅助数组: • closedge[ ]用于存放生成树外各顶点(V-U中各顶点)到生成树顶点集合内顶点(U中各顶点)的各边的当前最小权值; struct { VertexType adjvex; VRType lowcost; } closedge[MAX_VERTEX_NUM]; • 对于每个顶点vi∈V-U,在数组closedge对应一个相应分量closedge[ i-1 ]。其中: • closedge[ i-1 ].lowcost = Min{ cost(vi, u) | u∈U } 7-37
7.4 图的连通性 • Prim 算法框架描述如下 • 从连通网络 N ={V,E }中的选择任一顶点 u0加入到生成树T的顶点集合U中,并为集合V-U中的各顶点置最小权值边. • while (生成树T中顶点数目 < n) { 从集合V-U中各顶点对应的最小权值边中选取最短边(u,v); 将边(u,v) 及其在集合V-U中的顶点 v,加到生成树 T 中; 调整集合V-U中各顶点对应的最小权值边; } 7-38
void MiniSpanTree_PRIM(MGraph G, VertexType u) { int i, j, k ; minside closedge; k = LocateVex(G, u); for( j=0; j<G.vexnum; ++j){ // 辅助数组初始化 if( j!=k ){ closedge[j].adjvex = u ; closedge[j].lowcost=G.arcs[k][j].adj; } } closedge[k].lowcost = 0; // U={u} for(i=1; i<G.vexnum; ++i){ k = minimum( closedge ); // 求出T的下一个结点 // 输出生成树的边 printf( closedge[k].adjvex,G.vexs[k]); closedge[k].lowcost = 0; // 第K顶点并入U集 for( j=0; j<G.vexnum; ++j ) if( G.arcs[k][j].adj < closedge[j].lowcost ) closedge[j].lowcost = G.arcs[k][j].adj; } } 7.4 图的连通性 • 普里姆算法(邻接矩阵表示法): 7-39
7.5 有向无环图及其应用 7.5.1拓扑排序 • 拓扑排序(topological sort) 将一个有向无环图中所有顶点在不违反先决条件关系的前提下排成线性序列的过程称为拓扑排序。 • 拓扑序列 对于有向无环图G=(V,E),V里顶点的线性序列称作一个拓扑序列,如果该顶点序列满足: • 若在有向无环图G中从顶点Vi到Vj有一条路径,则在序列中顶点Vi必在顶点Vj之前。 7-40
7.5 有向无环图及其应用 • 例:课程安排 7-41
7.5 有向无环图及其应用 • 任何无环有向图,其顶点都可以排在一个拓扑序列里,其拓扑排序的方法是: • 从图中选择一个入度为0的顶点且输出之。 • 从图中删掉此顶点及其所有的出边。 • 回到第 (1) 步继续执行。 7-42
//边e的终点的入度值减1 for(Edge e=G.FirstEdge(V);G.IsEdge(e);e=G.NextEdge(e)) { G.Indegree[G.ToVertex(e)] - -; if(G.Indegree[G.ToVertex(e)]==0) Q.push(G.ToVertex(e)); //入度为0的顶点入队 } } for(i=0; i<G.VerticesNum(); i++) { if(G.Mark[i]==UNVISITED) { Print(“图有环”); //图有环 break; } } } //队列方式实现的拓扑排序 void TopsortbyQueue(Graph& G) { for(int i=0;i<G.VerticesNum();i++) G.Mark[i]=UNVISITED; //初始化标记数组 using std::queue; queue<int> Q; //初始化队列 for(i=0; i<G.VerticesNum(); i++) { //图中入度为0的顶点入队 if(G.Indegree[i]==0) Q.push(i); } while(!Q.empty()) //如果队列中还有图的顶点 { int V=Q.front();//顶部元素 Q.pop();//一个顶点出队 Visit(G, V);//访问该顶点 G.Mark[V]=VISITED; 7.5 有向无环图及其应用 7-43
7.5 有向无环图及其应用 //深度优先搜索实现的拓扑排序 void Do_topsort(Graph& G, int V, int *result,int& tag) { G.Mark[V]= VISITED; for(Edge e= G.FirstEdge(V); G.IsEdge(e);e=G.NextEdge(e)) { //访问V邻接到的所有未被访问过的顶点 if(G.Mark[G.ToVertex(e)]== UNVISITED) //递归调用 Do_topsort(G, G.ToVertex(e),result,tag); } result[tag++]=V; } //深度优先搜索方式实现的拓扑排序,结果是颠倒的 void TopsortbyDFS(Graph& G) { //对图所有顶点的标志位进行初始化 for(int i=0; i<G.VerticesNum(); i++) G.Mark[i]=UNVISITED; int *result=new int[G.VerticesNum()]; int tag=0; //对图的所有顶点进行处理 for(i=0; i<G.VerticesNum(); i++) if(G.Mark[i]== UNVISITED) Do_topsort(G,i,result,tag); //调用递归函数 for(i=G.VerticesNum()-1;i>=0;i--) //逆序输出 Visit(G, result[i]); } 注:深度优先搜索方式实现的拓扑排序无法判断图是否存在环。 7-44
7.5 有向无环图及其应用 • 拓扑排序的时间复杂度 • 图的每条边处理一次 • 图的每个顶点访问一次 所以,深度优先搜索方式实现的拓扑排序的时间复杂度同图的深度优先搜索方式遍历。 队列方式的拓扑排序:在有环的情况下会提前退出,从而可能没处理完所有的边和顶点。 7-45
7.5 有向无环图及其应用 • 队列方式拓扑排序的时间复杂度 关键是,算法开始时找出所有入度为0的顶点(同时可知道其它顶点的入度) • 当采用相邻矩阵时,算法开始时找所有入度为0的顶点需要 O(|V|2)的时间,加上处理边、顶点的时间,总代价为O(|V|2+|E|+|V|)= O(|V|2) • 当采用邻接表时,因为在顶点表的每个顶点中可以有一个字段来存储入度,所以只需O(|V|)的时间,加上处理边、顶点的时间,总代价为O(2|V|+|E|) 7-46
7.6 最短路径 • 单源最短路径(single-source shortest paths) • 指的是对已知图G=(V,E),给定源顶点s∈V,找出s到图中其它各顶点的最短路径。 • 每对顶点间的最短路径(all-pairs shortest paths) • 指的是对已知图G=(V,E),任意的顶点Vi,Vj∈V,找出从Vi到Vj的最短路径。 7-47
7.6 最短路径 • Dijkstra算法基本思想: • 把图中所有顶点分成两组 • 第一组包括已确定最短路径的顶点 • 第二组包括尚未确定最短路径的顶点; • 按最短路径长度递增的顺序逐个把第二组的顶点加到第一组中,直至从s出发可以到达的所有顶点都包括进第一组中。 • 在这个过程中,总保持从s到第一组各顶点的最短路径长度都不大于从s到第二组的任何顶点的最短路径长度,而且,每个顶点都对应一个距离值: • 第一组的顶点对应的距离就是从s到该顶点的最短路径长度 • 第二组的顶点对应的距离值是从s到该顶点的只包括第一组的顶点为中间顶点的最短路径长度 7-48
7.6 最短路径 • Dijkstra算法的具体做法: • 一开始第一组只包括顶点s,第二组包括其它所有顶点; • s对应的距离值为0,而第二组的顶点对应的距离值这样确定: • 若图中有边<s,Vi>或者(s,Vi),则Vi的距离值为此边所带的权,否则Vi的距离值为∞。 • 然后,每次从第二组的顶点中选一个其距离值为最小的顶点Vm加入到第一组中; • 每往第一组加入一个顶点Vm,就要对第二组的各顶点的距离值进行一次修正: • 若加进Vm做中间顶点,使从s到Vi的最短路径比不加Vm的为短,则需要修改Vi的距离值。 • 修改后再选距离值最小的顶点加入到第一组中,如此进行下去,直到图的所有顶点都包括在第一组中或者再也没有可加入到第一组的顶点存在。 7-49
7.6 最短路径 例:求下图中顶点V1到其它各顶点的最短路径 7-50