彻底弄懂最短路径问题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
彻底弄懂最短路径问题
只想说:温故而知新,可以为师矣。我大二的《数据结构》是由申老师讲的,那时候不怎么明白,估计太理论化了(ps:或许是因为我睡觉了);今天把老王的2011年课件又看了一遍,给大二的孩子们又讲了一遍,随手谷歌了N多资料,算是彻底搞懂了最短路径问题。请读者尽情享用……
我坚信:没有不好的学生,只有垃圾的教育。不过没有人理所当然的对你好,所以要学会感恩。
一.问题引入
问题:从某顶点出发,沿图的边到达另一顶点所经过的路径中,各边上权
值之和最小的一条路径——最短路径。解决最短路的问题有以下算法,Dijkstra 算法,Bellman-Ford算法,Floyd算法和SPFA算法,另外还有著名的启发式搜索算法A*,不过A*准备单独出一篇,其中Floyd算法可以求解任意两点间的最短路径的长度。笔者认为任意一个最短路算法都是基于这样一个事实:从任意节点A到任意节点B的最短路径不外乎2种可能,1是直接从A到B,2是从A经过若干个节点到B。
二.Dijkstra算法
该算法在《数据结构》课本里是以贪心的形式讲解的,不过在《运筹学》
教材里被编排在动态规划章节,建议读者两篇都看看。
观察右边表格发现除最后一个节点外其他均已经求出最短路径。
(1) 迪杰斯特拉(Dijkstra)算法按路径长度(看下面表格的最后一行,就是next点)递增次序产生最短路径。先把V 分成两组:
∙S:已求出最短路径的顶点的集合
∙V-S=T:尚未确定最短路径的顶点集合
将T中顶点按最短路径递增的次序加入到S中,依据:可以证明V0到T 中顶点Vk的最短路径,或是从V0到Vk 的直接路径的权值或是从V0经S中顶点到Vk的路径权值之和(反证法可证,说实话,真不明白哦)。
(2) 求最短路径步骤
1.初使时令S={V0},T={其余顶点},T中顶点对应的距离值,若存在
2.从T中选取一个其距离值为最小的顶点W(贪心体现在此处),加入S(注意不是直接从S集合中选取,理解这个对于理解vis数组的作用至关重要),对T中顶点的
距离值进行修改:若加进W作中间顶点,从V0到Vi的距离值比不加W的路径要短,则修改此距离值(上面两个并列for 循环,使用最小点更新)。
3.重复上述步骤,直到S中包含所有顶点,即S=V为止(说明最外层是除起点外的遍历)。
下面是上图的求解过程,按列来看,第一列是初始化过程,最后一行是每次求得的next点。
(3) 问题:Dijkstar能否处理负权边?(来自《图论》)
答案是不能,这与贪心选择性质有关(ps:貌似还是动态规划啊,晕了),每次都找一个距源点最近的点(dmin),然后将该距离定为这个点到源点的最短路径;但如果存在负权边,那就有可能先通过并不是距源点最近的一个次优点
(dmin'),再通过这个负权边L(L<0),使得路径之和更小(dmin'+L 0,3,4 3,0,-2 4,-2,0,用dijkstra求得d[1,2]=3,事实上d[1,2]=2,就是通过了1-3-2使得路径减小。不知道讲得清楚不清楚。二.Floyd算法 参考了南阳理工牛帅(目前在新浪)的博客。 Floyd算法的基本思想如下:从任意节点A到任意节点B的最短路径不外乎 2种可能,1是直接从A到B,2是从A 经过若干个节点到B,所以,我们假设dist(AB)为节点A到节点B的最短路径的距离,对于每一个节点K,我们检查 dist(AK) + dist(KB) 很简单吧,代码看起来可能像下面这样: for (int i=0; i for (int j=0; j for (int k=0; k if (dist[i][k] + dist[k][j] dist[i][j] = dist[i][k] + dist[k][j]; } } } } 但是这里我们要注意循环的嵌套顺序,如果把检查所有节点K放在最内层,那么结果将是不正确的,为什么呢?因为这样便过早的把i到j的最短路径确定下来了,而当后面存在更短的路径时,已经不再会更新了。 让我们来看一个例子,看下图: 图中红色的数字代表边的权重。如果我们在最内层检查所有节点K,那么对于A->B,我们只能发现一条路径,就 是A->B,路径距离为9,而这显然是不正确的,真实的最短路径是A->D->C->B,路径距离为6。造成错误的原因就是我们把检查所有节点K放在最内层,造成过早的把A到B的最短路径确定下来了,当确定A->B的最短路径时dist(AC)尚未被计算。所以,我们需要改写循环顺序,如下: ps:个人觉得,这和银行家算法判断安全状态(每种资源去测试所有线程),树状数组更新(更新所有相关项)一样的 思想。 for (int k=0; k for (int i=0; i for (int j=0; j /* 实际中为防止溢出,往往需要选判断dist[i][k]和dist[k][j 都不是Inf ,只要一个是Inf,那么就肯定不必更新。 */