三种路径算法:Dijstra、Bellman-Ford、Floyd-Warshall

#.Dijstra算法:

    求单源最短路径,不允许出现负边。n-1次外循环。

    流程:

        1.将所有节点分为:原点集合S、剩余节点集合D,将源节点装入S,用dist记录各个节点的距离。

        2.每轮循环都从D中找出当前距离最小的点a,移入S,同时更新D中与a相邻的节点的最小距离:

                dist[p] = Math.min(dist[p], dist[a] + E(a,p) );

        n-1轮循环后,得到的dist记录着所有节点的最小距离。

        复杂度与实现代码相关,一般情况下O(V^2);若使用最小堆来存放剩余节点的距离,则复杂度为O( (V+E)*logV ),其中V为顶点数,E为边数。

    原理解释:

        整个过程可以在脑海中想象一下,从源节点出发,生长出来一棵树,所有还未蔓延到的节点,距离在记录中暂时都是无穷大的;

        其实每次都是在树的叶子节点中找距离最小的结点a,然后从D中移出,放入S,再把D中与a相邻的节点更新最小距离,纳入树中。

        为何每次树的叶子节点中距离最小的节点a, 其此刻记录的距离就是a最终的最小距离?

        因为图中没有负边,if dist[b] >= dist[a], 那么途径b到达a的任何路径都会 dist[经过b] >= dist[a];

                又dist[a]是此刻所有叶子节点中最小的,而D中剩余的非叶子节点都需要途径叶子节点才能到达源节点,

                那么dist[a] <= dist[其它叶子节点] <= dist[D中其它非叶子节点]

        因此,dijstra算法每次都会选出一个节点来,而且这些节点的选择顺序也是按照最终距离从小到大排布的。

        

            但当图中可能存在负边时,就无法再保证dist[a] <= dist[D中其它非叶子节点],因为更远的地方可能存在一条负值很大的边,

        导致dist[D中其它某个非叶子节点c] < dist[a], 那么途径这个节点c到达a的某条路径,也许距离会小于此刻a记录中的距离。

        因此Dijstra算法就不再成立。

    

#.Bellman-Ford算法:

    求单源最短路径,允许出现负边。无负环情况下,用dist记录各个节点的距离,V-1次外循环得到结果。

    若V-1次后继续外循环可得到任意节点更小的距离,说明图中存在负环。

    流程:

        每轮外循环,都使用各条边进行判断,寻找更短距离:

            dist[p] = Math.min( dist[p], dist[a] + E(a,p) );

    复杂度O(V*E),其中V为顶点数,E为边数。

    原理解释:

        当图中没有负环时,任何环都是非负的,那么对于从源节点s到节点d的任意路径:dist[不包含环] <= dist[包含环],因此最终最小路径是不含环的,路径最多包含V个节点,也就是走了V-1次。从s到d有很多种走法,但按照一共走了多少步,最多可分为V-1种:走一步、走两步、……、走V-1步。

        整个过程可以在脑海中想象一下,从源节点s出发,生长出来一棵生成树,所有还未蔓延到的节点,距离在记录中暂时都是无穷大的。

        第一步:用所有边迭代一遍,所有与s相连的边起到效果,将树生长起来,获得了走1步后树中各节点最小距离dp[1];

        第二步:用所有边迭代一遍,所有与树相连的边起到效果,树进一步成长,并获得了走2步时树中各节点的可能最小距离,与上面dp[1]相比较,可得到最多走2步时树上个节点的最小距离dp[2];

        第二步:用所有边迭代一遍,所有与树相连的边起到效果,树进一步成长,并获得了走3步时树中各节点的可能最小距离,与上面dp[2]相比较,可得到最多走3步时树上个节点的最小距离dp[3];

        依次类推…….

        第n-1步:用所有边迭代一遍,所有与树相连的边起到效果,树进一步成长,并获得了走(V-1)步时树中各节点的可能最小距离,与上面dp[V-2]相比较,可得到最多走(V-1)步时树上个节点的最小距离dp[V-1]。

        这时如果图上所有节点是联通的,那么已经获得最终结果。如果还有节点距离为无穷大,则该节点与源节点不联通。

        若V-1次后继续循环可得到任意节点更小的距离,说明图中存在负环。

        

#.Floyd-Warshall算法:

    求全源最短路径,允许出现负边,无负环情况下,n次外循环得到结果。若n次后继续外循环可得到任意节点更小的距离,说明图中存在负环。

    流程:

        伪代码:

        外循环,允许途经顶点k: 1~V

            源点i:1~V

                目标点j:1~V

                        判断途经k后,从源点到目标点是否有更小距离: dist[i][j] = Math.min( dist[i][j], dist[i][k] + dist[k][j] );         

        每轮外循环,都使用各条边进行判断,寻找更短距离。复杂度O(V^3)。

    原理解释:

        当图中没有负环时,任何环都是非负的,那么对于从任意节点a到节点b的任意路径:dist[不包含环] <= dist[包含环],因此最终最小路径是不含环的,路径最多包含V个节点。从a到b有很多种走法,但按照一共允许途径多少个节点,可分为V种:允许途径1个节点、允许途径节点1~节点2、.......、允许经过节点1~节点V。

        例如一张图中包含a、b、c、d、e五个顶点,dis[][]记录全源最小路径:

        0:初始时,不允许经过任何节点,当前获得的是不经过任何节点时的最小距离dist[][];

        1: 尝试途径节点a获取新的距离来优化,与上面的dist[][]相比较保留较小值,得到的是允许经过节点[a]时的最小距离dist[][]。(至于不是最小距离的所有其它路径,都没有记录下来的必要。因为当当我们尝试途径节点k来优化i到j的距离时,只有尝试最小的dist[i][k]、dist[k][j]才有可能最小,否则dist[i][k] + dist[k][j]一定不是最小的。)

        2: 尝试途径节点b获取新的距离来优化,因为所有与b相连的路径在前面所有的步骤优化后,实际上已经是允许经过[a]的最优路径[a]b,与上面的dist[][]相比较保留较小值,得到的是允许经过节点[ab]时的最小距离dist[][]。

        3: 尝试途径节点b获取新的距离来优化,因为所有与c相连的路径在前面所有的步骤优化后,实际上已经是允许经过[ab]的最优路径[ab]c,与上面的dist[][]相比较保留较小值,得到的是允许经过节点[abc]时的最小距离dist[][]。

        依次类推….最终把所有节点都拿来尝试优化后,得到允许经过节点[abcde]时的最小距离。

       结论,n轮外循环,已经尝试了所有需要尝试的情况,最终的结果恰好是允许途径所有节点情况下的全源最小路径。

猜你喜欢

转载自blog.csdn.net/u013914309/article/details/124803690
今日推荐