最短路径
6.6.2最短路径
假若要在计算机上建立一个交通咨询系统,则可以采用图的结构来表示实际的交通网络。如 图 6.21 所示,图中顶点表示城市,边表示城市间的交通联系。例如,一位旅客要从 A 城到 B 城, 他希望选择一条中转次数最少的路线。假设图中每一站都需要换车,则这个问题反映到图上就是 要找一条从顶点 A 到 B 所含边的数目最少的路径。只需从顶点 A 出发对图做广度优先搜索,一 旦遇到顶点B就终止。由此所得的广度优先生成树上,从根顶点A到顶点B的路径就是中转次 数最少的路径,路径上A与B之间的顶点数就是中转次数,但是,这只是一类最简单的图的最 短路径问题。有时,对于旅客来说,可能更关心的是节省交通费用;而对千司机来说,里程和 速度则是他们感兴趣的信息。为了在图上表示有关信息,可对边赋以权,权的值表示两城市间的 距离,或途中所需时间,或交通费用等。此时路径长度的度最就不再是路径上边的数目,而是路 径上边的权值之和。考虑到交通图的有向性,例如,汽车的上山和下山,轮船的顺水和逆水,所 花费的时间或代价就不相同,所以交通网往往是用带权有向网表示。在带权有向网中, 习惯上称 路径上的第一个顶点为源点(Source), 最后一个顶点为终点(Destination)。
1.从某个源点到其余各顶点的最短路径
算法6.10 迪杰斯特拉算法
-
算法步骤
1.初始化:
- 将源点 Vo加到S中,即S[v0] = true ;
- 将 v。到各个终点的最短路径长度初始化为权值,即 D[i] = Garcsv0, (vi属于V-S);
- 如果 v。和顶点v之间 有弧,则将 Vi的前驱置为 Vo, 即 Path[i] = v0 , 否则 Path[i] = -1。
2.循环n-1次,执行以下操作:
- 选择下一条最短路径的终点 Vk, 使得:D[k] = Min
- 将 Vk加到S中,即S[vk] =true;
- 根据条件更新从 v0出发到集合 v-s 上任一顶点的最短路径的长度,若条件D[k] + Gares [k] [i]<D[i]成立,则更新 D[i] = D[k] +Garcs[k] [i], 同时更改 Vi的前驱为vk; Path [i] = k。
-
算法描述
void ShortestPath_DIJ(AMGraph G, int vO) {//用Dijkstra算法求有向网G的vO顶点到其余顶点的最短路径 n=G.vexnum; //n为G 中顶点的个数 for(v= O;v<n; ++v) //n个顶点依次初始化 { S[v]=false; //S初始为空集 D[v]=G.arcs[vO][v]; //将vO到各个终点的最短路径长度初始化为弧上的权值 if(D[v]<Maxlnt) Path[v]=vO; //如果V0和V之间有弧,则将V的前驱置为V0 else Path[v]=-1 //如果vO 和v之间无弧, 则将v的前驱置为一1 } S[vO] = true; //将V0加入S D[vO]=0; //源点到源点的距离为0 /*-----初始化结束, 开始主循环, 每次求得vO到某个顶点v的最短路径, 将v加到s集----*/ for( i=l; i<n;++i) //对其余 n-1个顶点,依次进行计算 { min= Maxlnt; for(w= O;w<n;++w) if (!S[w] &&D [w] <min) {v=w;min=D[w];} //选择一条当前的最短路径,终点为v S[v]=true; //将v加入S for(w=O;w<n;++w) //更新从v。出发到集合v-s上所有顶点的最短路径长度 if (! S [w) && (D [v) +G. arcs [v) [w) <D [w])) { D[w] =D [v] +G.arcs[v][w]; //更新 D[w] Path[w]=v; //更改w的前驱为v } } }
2.每一对顶点之间的最短路径
求解每一对顶点之间的最短路径有两种方法:其一是分别以图中的每个顶点为源点共调用n次 迪杰斯特拉算法;其二是采用下面介绍的弗洛伊德(Floyd)算法。 两种算法的时间复杂度均为O(n3 ), 但后者形式上较简单。
弗洛伊德算法仍然使用带权的邻接矩阵 arcs 来表示有向网G,求从顶点V; 到VJ的最短路径。算法的实现要引入以 下辅助的数据结构。
(1) 二维数组 Path[i] [j]:最短路径上顶点 VJ 的前一顶点的序号。
(2)二维数组D[i] [j]:记录顶点 Vi和vj之间的最短路径长度。
算法6.11 费罗伊德算法
-
算法描述
void ShortestPath_Floyd(AMGraph G) {//用Floyd算法求有向网G中各对顶点1和)之间的最短路径 for (i=O; i < G. vexnum; ++i) //各对结点之间初始已知路径及距离 for(j=O;j <G.vexnum;++j) { D[i][j] =G.arcs [i][j]; if(D[i] [j]<Maxint) Path[i] [j]=i; //如果i和j之间有弧,则将j的前驱置为1. else Path[i] [j]=-1; //如果i和j之间无弧,则将j的前驱置为-1 } for (k=O; k < G. vexnum; ++k) for (i=O; i <G.vexnum;++i) for(j=O;j <G.vexnum;++j) if(D[i] [k]+D[k][j] <D[i][j]) //从i经k到j的一条路径更短 { D[i][j]=D[i][k]+D[k][j]; //更新D[i][j] Path[i][j]=Path[k][j]; //更改j的前驱为K } }
6.6.3拓扑排序
1.AOV-网
一个无环的有向图称作有向无环图( DirectedAcycline Graph), 简称DAG图。有向无环图是描述一项工程或系统的进行过程的有效工具。通常把计划、 施工过程、 生产流程、 程序流程等都当成一个工程。除了很小的工程外,一般的工程都可分为若干个称做活动(Activity)的子工程,而这些子工程之间, 通常受着一定条件的约束, 如其中某些子工程的开始必须在另一些子工程完成之后。
2.拓扑排序的过程
(1) 在有向图中选一个无前驱的顶点且输出它。
(2)从图中删除该顶点和所有以它为尾的弧。
(3) 重复 (1) 和 (2), 直至不存在无前驱的顶点。
(4)若此时输出的顶点数小千有向图中的顶点数,则说明有向图中存在环, 否则输出的顶点序列即为一个拓扑序列。
3.拓扑排序的实现
(1)一维数组 indegree[i]: 存放各顶点入度,没有前驱的顶点就是入度为零的顶点。 删除顶点及以它为尾的弧的操作,可不必真正对图的存储结构进行改变,可用弧头顶点的入度减l的办法来实现。
(2)栈S: 暂存所有入度为零的顶点,这样可以避免重复扫描数组indegree检测入度为0的顶点, 提高算法的效率。
(3)一维数组topo(i]: 记录拓扑序列的顶点序号。
算法6.12 拓扑排序
-
算法步骤
- 求出各顶点的入度存入数组 indegree[i]中, 并将入度为0 的顶点入栈。
- 只要栈不空, 则重复以下操作:
- 将栈顶顶点 vi出栈并保存在拓扑序列数组 topo 中;
- 对顶点 V;的每个邻接点 Vk的入度减1, 如果 Vk的入度变为0, 则将 Vk入栈。
- 如果输出顶点个数少于AOV-网的顶点个数, 则网中存在有向环, 无法进行拓扑排序, 否则拓扑排序成功。
-
算法描述
Status TopologicalSort(ALGraph G,int topo[]) {//有向图G采用邻接表存储结构 //若 G 无回路,则生成 G 的一个拓扑序列 topo []并返回 OK, 否则 ERROR FindinDegree(G,indegree); //求出各顶点的入度存入数组 indegree中 InitStack(S) //栈 s初始化为空 for(i=O;i<G.vexnum;++i) if (! indegree [i)) Push (S, i);//入度为0者进栈 m=O; //对输出顶点计数,初始为0 while (! StackEmpty (S)) //栈s非空 { Pop (S, i); //将栈顶顶点Vi出栈 topo[m]=i; //将Vi保存在拓扑序列数组 topo中 ++m; //对输出顶点计数 p=G.vertices[i) .firstarc; //p指向Vi的第一个邻接点 while (p!=NULL) { k=p->adjvex; //vk为 m 的邻接点 --indegree[k]; //vi的每个邻接点的入度减1 if(indegree[k)==O) Push(S,k); //若入度减为0, 则入栈 p=p->nextarc; //vi的每个邻接点的入度减1 } } if(m<G.vexnum) return ERROR; //该有向图有回路 else return OK; }
6.6.4关键路径
1.AOE-网
与AOV-网相对应的是AOE-网 (Activity On Edge) , 即以边表示活动的网。 AOE-网是一个带权的有向无环图, 其中, 顶点表示事件, 弧表示活动, 权表示活动持续的时间。 通常, AOE-网可用来估算工程的完成时间。
2.关键路径求解的过程
(1)对图中顶点进行排序, 在排序过程中按拓扑序列求出每个事件的最早发生时间ve(i)。
( 2 )按逆拓扑序列求出每个事件的最迟发生时间vl(i)。
(3) 求出每个活动 a;的最早开始时间e(i)。
(4) 求出每个活动 a;的最晚开始时间l(i)-
( 5 )找出 e(i)= l(i)的活动a;, 即为关键活动。由关键活动形成的 由源点到汇点 的每一条路径就是关键路径, 关键路径有可能不止一条 。
3.关键路径算法的实现
算法6.13 关键路径算法
-
算法描述
Status CriticalPath(ALGraph G) {//G为邻接表存储的有向网,输出G的各项关键活动 if(!TopologicalOrder(G,topo)) return ERROR; //调用拓扑排序算法,使拓扑序列保存在topo中,若调用失败, 则存在有向环, 返回ERROR n=G. vexnum; //n为顶点个数 for(i=O;i<n; i++) //给每个事件的最早发生时间置初值0 /*- - - ----- -- - 按拓扑次序求每个事件的最早发生时间 - - - - - - ----*/ for (i=O; i<n; i++) { k=topo[i]; //取得拓扑序列中的顶点序号K p=G.vertices[k].firstarc; //p指向k的第一个邻接顶点 while(p!=NULL); { //依次更新k的所有邻接顶点的最早发生时间 j=p->adjvex; //j为邻接顶点的序号 if(ve[j]<ve[k]+p->weight)//更新顶点j的最早发生时间 ve[j] ve[j]=ve[k]+p->weight; p=p->nextarc; //p指向k的下一个邻接顶点 } } for(i=O;i<n;i++) //给每个事件的最迟发生时间置初值 ve[n-1] vl[i]=ve[n-1]; /*-------------按逆拓扑次序求每个事件的最迟发生时间--- ----- ------*/ for(i=n-l;i>=O;i--) { k=topo[i]; //取得拓扑序列中的顶点序号K p=G.vertices[k) .firstarc;//p指向k的第一个邻接顶点 while(p!=NULL) //根据k的邻接点,更新k的最迟发生时间 { j=p->adjvex; //j为邻接顶点的序号 if(vl[k]>vl[j)-p->weight) //更新顶点 K 的最迟发生时间 vl [k] vl[k)=vl[j)-p->weight; p=p->nextarc; //p指向k的下一个邻接顶点 } } /*---------- - - - -- -判断每一活动是否为关键活动-------*/ for(i=O;i<n;i++) //每次循环针对m为活动开始点的所有活动 { p=G.vertices[i) .firsarc;//p指向1的第一个邻接项点 while (p ! =NULL) { j=p->adjvex; //j为i的邻接顶点的序号 e=ve[i]; //计算活动<Vi, Vj>的最早开始时间 l=vl[j)-p->weight; //计算活动<Vi, Vj>的最迟开始时间 if (e==l) //若为关键活动,则输出<vi, Vj> cout<<G.vertices[i].data<<G.vertices[j] .data; p=p->nextarc; //p指向 i 的下一个邻接顶点 } } }

浙公网安备 33010602011771号