




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、彻底弄懂最短路径问题 只想说:温故而知新,可以为师矣。我大二的数据结构是由申老师讲的,那时候不怎么明白,估计太理论化了(ps:或许是因为我睡觉了);今天把老王的2011年课件又看了一遍,给大二的孩子们又讲了一遍,随手谷歌了N多资料,算是彻底搞懂了最短路径问题。请读者尽情享用 我坚信:没有不好的学生,只有垃圾的教育。不过没有人理所当然的对你好,所以要学会感恩。一.问题引入
2、 问题:从某顶点出发,沿图的边到达另一顶点所经过的路径中,各边上权值之和最小的一条路径最短路径。解决最短路的问题有以下算法,Dijkstra算法,Bellman-Ford算法,Floyd算法和SPFA算法,另外还有著名的启发式搜索算法A*,不过A*准备单独出一篇,其中Floyd算法可以求解任意两点间的最短路径的长度。笔者认为任意一个最短路算法都是基于这样一个事实:从任意节点A到任意节点B的最短路径不外乎2种可能,1是直接从A到B,2是从A经过若干个节点到B。二.Dijkstra算法 该算
3、法在数据结构课本里是以贪心的形式讲解的,不过在运筹学教材里被编排在动态规划章节,建议读者两篇都看看。 观察右边表格发现除最后一个节点外其他均已经求出最短路径。 (1) 迪杰斯特拉(Dijkstra)算法按路径长度(看下面表格的最后一行,就是next点)递增次序产生最短路径。先
4、把V分成两组:· S:已求出最短路径的顶点的集合· V-S=T:尚未确定最短路径的顶点集合 将T中顶点按最短路径递增的次序加入到S中,依据:可以证明V0到T中顶点Vk的最短路径,或是从V0到Vk的直接路径的权值或是从V0经S中顶点到Vk的路径权值之和(反证法可证,说实话,真不明白哦)。 (2) 求最短路径步骤1. 初使时令 S=V0,T=其余顶点,T中顶点对应的距离值, 若存
5、在<V0,Vi>,为<V0,Vi>弧上的权值(和初始化方式不同),若不存在<V0,Vi>,为Inf。2. 从T中选取一个其距离值为最小的顶点W(贪心体现在此处),加入S(注意不是直接从S集合中选取,理解这个对于理解vis数组的作用至关重要),对T中顶点的距离值进行修改:若加进W作中间顶点,从V0到Vi的距离值比不加W的路径要短,则修改此距离值(上面两个并列for循环,使用最小点更新)。3. 重复上述步骤,直到S中包含所有顶点,即S=V为止(说明最外层是除起点外的遍历)。 下面
6、是上图的求解过程,按列来看,第一列是初始化过程,最后一行是每次求得的next点。 (3) 问题:Dijkstar能否处理负权边?(来自图论) 答案是不能,这与贪心选择性质有关(ps:貌似还是动态规划啊,晕了),每次都
7、找一个距源点最近的点(dmin),然后将该距离定为这个点到源点的最短路径;但如果存在负权边,那就有可能先通过并不是距源点最近的一个次优点(dmin'),再通过这个负权边L(L<0),使得路径之和更小(dmin'+L<dmin),则dmin'+L成为最短路径,并不是dmin,这样dijkstra就被囧掉了。比如n=3,邻接矩阵: 0,3,4 3,0,-2 4,-2,0,用dijkstra求得d1,2=3,事实上d1,2=2,就是通过了1-3-2使得路径减小。不知道讲得清楚不清楚。二.Floyd算法
8、; 参考了南阳理工牛帅(目前在新浪)的博客。 Floyd算法的基本思想如下:从任意节点A到任意节点B的最短路径不外乎2种可能,1是直接从A到B,2是从A经过若干个节点到B,所以,我们假设dist(AB)为节点A到节点B的最短路径的距离,对于每一个节点K,我们检查dist(AK) + dist(KB) < dist(AB)是否成立,如果成立,证明从A到K再到B的路径比A直接到B的路径短,我们便设置 dist(AB) = dist(AK) + dist(KB),
9、这样一来,当我们遍历完所有节点K,dist(AB)中记录的便是A到B的最短路径的距离。 很简单吧,代码看起来可能像下面这样:for (int i=0; i<n; +i) for (int j=0; j<n; +j) for (int k=0; k<n; +k) if (distik + distkj < distij ) distij = distik + distkj; 但是这里我们要注意循环的嵌套顺
10、序,如果把检查所有节点K放在最内层,那么结果将是不正确的,为什么呢?因为这样便过早的把i到j的最短路径确定下来了,而当后面存在更短的路径时,已经不再会更新了。 让我们来看一个例子,看下图: 图中红色的数字代表边的权重。如果我们在最内层检查所有节点K,那么对于A->B,我们只能发现一条路径,就是A->B,路径距离为9,而这显然是不正确的,真实的最短路径是A->D->C->B,路径距离为6。造成错误的原
11、因就是我们把检查所有节点K放在最内层,造成过早的把A到B的最短路径确定下来了,当确定A->B的最短路径时dist(AC)尚未被计算。所以,我们需要改写循环顺序,如下: ps:个人觉得,这和银行家算法判断安全状态(每种资源去测试所有线程),树状数组更新(更新所有相关项)一样的思想。for (int k=0; k<n; +k) for (int i=0; i<n; +i) for (int j=0; j<n; +j) /* 实际中为防止溢出,往往需要选判断 distik和distkj 都不是
12、Inf ,只要一个是Inf,那么就肯定不必更新。 */ if (distik + distkj < distij ) distij = distik + distkj; 如果还是看不懂,那就用草稿纸模拟一遍,之后你就会豁然开朗。半个小时足矣(早知道的话会节省很多个半小时了。) 再来看路径保存问题:void floyd() for(int i=1; i<=n ; i+) for(int j=1; j<= n; j+) if
13、(mapij=Inf) pathij = -1;/表示 i -> j 不通 else pathij = i;/ 表示 i -> j 前驱为 i for(int k=1; k<=n; k+) for(int i=1; i<=n; i+) for(int j=1; j<=n; j+) if(!(distik=Inf|distkj=Inf)&&distij > distik + distkj) distij = distik + distkj; pathik = i; pathij = pathkj; void printPath(int from
14、, int to) /* * 这是倒序输出,若想正序可放入栈中,然后输出。 * * 这样的输出为什么正确呢?个人认为用到了最优子结构性质, * 即最短路径的子路径仍然是最短路径 */ while(pathfromto!=from) System.out.print(pathfromto +""); to = pathfromto; 数据结构课本上的那种方式我现在还是不想看,看着不舒服 Floyd算法另一种理
15、解DP,为理论爱好者准备的,上面这个形式的算法其实是Floyd算法的精简版,而真正的Floyd算法是一种基于DP(Dynamic Programming)的最短路径算法。设图G中n 个顶点的编号为1到n。令c i, j, k表示从i 到j 的最短路径的长度,其中k 表示该路径中的最大顶点,也就是说ci,j,k这条最短路径所通过的中间顶点最大不超过k。因此,如果G中包含边<i, j>,则ci, j, 0 =边<i, j> 的长度;若i= j ,则ci,j,0=0;如果G中不包含边<i, j>,则c (i, j, 0)= +。ci, j, n 则是从i 到j 的
16、最短路径的长度。对于任意的k>0,通过分析可以得到:中间顶点不超过k 的i 到j 的最短路径有两种可能:该路径含或不含中间顶点k。若不含,则该路径长度应为ci, j, k-1,否则长度为 ci, k, k-1 +c k, j, k-1。ci, j, k可取两者中的最小值。状态转移方程:ci, j, k=minci, j, k-1, c i, k, k-1+c k, j, k-1,k0。这样,问题便具有了最优子结构性质,可以用动态规划方法来求解。 看另一个DP(直接引用王老师课件) &
17、#160; 说了这么多,相信读者已经跃跃欲试了,咱们看一道例题,以ZOJ 1092为例:给你一组国家和国家间的部分货币汇率兑换表,问你是否存在一种方式,从一种货币出发,经过一系列的货币兑换,最后返回该货币时大于出发时的数值(ps:这就是所谓的投机倒把吧),下面是一
18、组输入。 3 /国家数 USDollar /国家名 BritishPound FrenchFranc 3 /货币兑换数 USDollar 0.5 BritishPound /部分货币汇率兑换表 BritishPound 10.0 FrenchFranc FrenchFranc 0.21 USDollar 月赛做的
19、题,不过当时用的思路是求强连通分量(ps:明明说的,那时我和华杰感觉好有道理),也没做出来,现在知道了直接floyd算法就ok了。 思路分析:输入的时候可以采用Map<String,Integer> map = new HashMap<String,Integer>()主要是为了获得再次包含汇率输入时候的下标以建图(感觉自己写的好拗口),或者第一次直接存入String数组str,再次输入的时候每次遍历str数组,若是equals那么就把str的下标赋值给该币种建图。下面就是floyd算法
20、啦,初始化其它点为-1,对角线为1,采用乘法更新求最大值。三.Bellman-Ford算法 为了能够求解边上带有负值的单源最短路径问题,Bellman(贝尔曼,动态规划提出者)和Ford(福特)提出了从源点逐次绕过其他顶点,以缩短到达终点的最短路径长度的方法。 Bellman-ford算法是求含负权图的单源最短路径算法,效率很低,但代码很容易写。即进行不停地松弛,每次松弛把每条边都更新一下,若n-1次松弛后还能更新,则说明图中有负环,无法得出结果,否则就成功完成。Bellman-ford算法有一个小优
21、化:每次松弛先设一个flag,初值为FALSE,若有边更新则赋值为TRUE,最终如果还是FALSE则直接成功退出。Bellman-ford算法浪费了许多时间做无必要的松弛,所以SPFA算法用队列进行了优化,效果十分显著,高效难以想象。SPFA还有SLF,LLL,滚动数组等优化。 关于SPFA,请看我这一篇 递推公式(求顶点u到源点v的最短路径): &
22、#160; dist 1 u = Edgevu dist k u = min dist k-1 u, min dist k-1 j + Edgeju , j=0,1,n-1,ju Dijkstra算法和Bellman算法思想有很大的区别:Dijkstra算法在求解过程中,源点到集合S内各顶点的最短路径一旦求出,则之后不变了,修改 的仅仅是源点到T集合中各顶点的最短路径长度。Bell
23、man算法在求解过程中,每次循环都要修改所有顶点的dist ,也就是说源点到各顶点最短路径长度一直要到Bellman算法结束才确定下来。 算法适用条件· 1.单源最短路径(从源点s到其它所有顶点v)· 有向图&无向图(无向图可以看作(u,v),(v,u)同属于边集E的有向图)· 边权可正可负(如有负权回路输出错误提示)· 差分约束系统(至今貌似只看过一道题) Bellma
24、n-Ford算法描述:1. 初始化:将除源点外的所有顶点的最短距离估计值 dv +, ds 02. 迭代求解:反复对边集E中的每条边进行松弛操作,使得顶点集V中的每个顶点v的最短距离估计值逐步逼近其最短距离;(运行|v|-1次,看下面的描述性证明(当做树))3. 检验负权回路:判断边集E中的每一条边的两个端点是否收敛。如果存在未收敛的顶点,则算法返回false,表明问题无解;否则算法返回true,并且从源点可达的顶点v的最短距离保存在dv中 描述性证明:(这个解释很好)
25、0; 首先指出,图的任意一条最短路径既不能包含负权回路,也不会包含正权回路,因此它最多包含|v|-1条边。其次,从源点s可达的所有顶点如果 存在最短路径,则这些最短路径构成一个以s为根的最短路径树。Bellman-Ford算法的迭代松弛操作,实际上就是按顶点距离s的层次,逐层生成这棵最短路径树的过程。在对每条边进行1遍松弛的时候,生成了从s出发,层次至多为1的那些树枝。也就是说,找到了与s至多有1条边相联的那些顶点的最短路径;对每条边进行第2遍松弛的时候,生成了第2层次的树枝,就是说找到了经过2条边相连的那些顶点的最短路径。因为最短路径最多只包含
26、|v|-1条边,所以,只需要循环|v|-1 次。每实施一次松弛操作,最短路径树上就会有一层顶点达到其最短距离,此后这层顶点的最短距离值就会一直保持不变,不再受后续松弛操作的影响。(但是,每次还要判断松弛,这里浪费了大量的时间,这就是Bellman-Ford算法效率底下的原因,也正是SPFA优化的所在)。,如图(没找到画图工具的射线),若是B和C的最短路径不更新,那么点D的最短路径肯定也无法更新,这就是优化所在。如果没有负权回路,由于最短路径树的高度最多只能是|v|-1,所以最多经过|v|-1遍松弛操作后,所有从s可达的顶点必将求出最短距离。如果 dv仍保持 +,则表明从s到v不可达。如果有负权
27、回路,那么第 |v|-1 遍松弛操作仍然会成功,这时,负权回路上的顶点不会收敛。 参考了图论。 问题:Bellman-Ford算法是否一定要循环n-1次么?未必!其实只要在某次循环过程中,考虑每条边后,都没能改变当前源点到所有顶点的最短路径长度,那么Bellman-Ford算法就可以提前结束了(开篇提出的小优化就是这个)。 上代码(参考了牛
28、帅的博客)#include<iostream>#include<cstdio>using namespace std;#define MAX 0x3f3f3f3f#define N 1010int nodenum, edgenum, original; /点,边,起点typedef struct Edge /边 int u, v; int cost;Edge;Edge edgeN;int disN, preN;bool Bellman_Ford() for(int i = 1; i <= nodenum; +i) /初始化 disi = (i = original
29、 ? 0 : MAX); for(int i = 1; i <= nodenum - 1; +i) for(int j = 1; j <= edgenum; +j) if(disedgej.v > disedgej.u + edgej.cost) /松弛(顺序一定不能反) disedgej.v = disedgej.u + edgej.cost; preedgej.v = edgej.u; bool flag = 1; /判断是否含有负权回路 for(int i = 1; i <= edgenum; +i) if(disedgei.v > disedgei.u + edgei.cost) flag = 0; break; return flag;void print_path(int root) /打印最短路的路径(反向) while(root != preroot) /前驱 print
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 风光储技术在农村分布式能源市场中的竞争态势分析报告
- 2025年项目施工合同补充协议书
- 2025海产品捕捞雇佣合同协议书
- 2025年房屋建筑项目建筑材料采购合同单
- 2025年汽车维修保养项目合作合同模板
- 2025中国专利许可合同
- 2025年销售业务员试用合同样本
- 2025年建筑合同预算调整补充协议书格式
- 2025年租赁及使用协议标准文本
- 2025年甲方工程劳动力合作合同
- 中级政工考试题库及答案
- (2025年标准)工作就业协议书
- 医疗公司加盟管理办法
- 2025年浙江省中考道德与法治试题答案详解讲评(课件)
- 如何用飞书高效讲解
- 广州南沙深化面向世界的粤港澳全面合作白皮书(2022.06-2025.06)
- 2025年陕西教师编制招聘考试笔试试题(含答案)
- 信息公开条例培训课件
- 2025年留疆战士考试题库及答案
- 新初一入学分班考试语文卷(含答案)
- 2025年全国《中小学教育管理》知识考试题库与答案
评论
0/150
提交评论