求最短路径算法系列

一 . 动态规划算法

  适用于求解多阶段决策过程中的最优化问题,必须满足最优化原理、无后效性和重叠性。使用动态规划只需要多项式时间复杂度,因此它比回溯法、暴力法等要快许多。

  • 划分阶段

  

  • 求解最短路径: 

    

  • 过程指标函数基本方程(和)

  

  • Javascript实现算法
    /**
     * 示例:s = {
    *                     4:{s:{C1:{T:3},C2:{T:4}}},
    *                     3:{s:{B1:{C1:1,C2:4},B2:{C1:6,C2:3},B3:{C1:3,C2:3}}},
    *                     2:{s:{A1:{B1:7,B2:4,B3:6},A2:{B1:3,B2:2,B3:4},A3:{B1:4,B2:1,B3:5}}},
    *                     1:{s:{Q:{A1:2,A2:4,A3:3}}},
    *          }
     *
     *       DynamicProgramming(4, s)
     *
     *       11:
     *       Q->A2->B1->C1->T;
     *       Q->A3->B1->C1->T;
     *       Q->A3->B2->C2->T;
     *
     * @param int k 阶段数
     * @param object s 阶段状态集
     * @return string
     */
    
    function DynamicProgramming(k, s) {
        let indicators = {},//过程指标函数集声明
            k0 = k,//阶段数登记
            f,//当前k子过程指标函数声明
            v;//阶段指标函数声明
    
        for( ; k > 0; k--) {
            f = {};
            for(let i in s[k].s) {
                f[i] = {//当前k子过程函数在状态s = i时
                    optValue:0,//最优值
                    opt:[],//最佳决策
                };
    
                for(let j in s[k].s[i]) {
                    v = indicators[k+1] ? indicators[k+1][j].optValue : 0;
                    v = s[k].s[i][j] + v;
                    if (!f[i].optValue || v < f[i].optValue) {
                        f[i].optValue = v;
                        f[i].opt = [j];
                    } else if (v == f[i].optValue) {
                        f[i].opt.push(j);
                    }
                }
            }
            indicators[k] = f;
        }
    
        return optPath();//输出最后结果
    
        function optPath(k = 0, s = '', preEquivalentPath = '') {//k:阶段数,s:状态,preEquivalentPath:等价路径前缀
            let results = '';//结果集初始化
    
            if (k == 0) {//结果集包含最优值
                for (let i in indicators[1]) {
                    results += indicators[1][i].optValue + ':\n' + optPath(1, i);
                }
            } else if (k > k0) {//结果集包含最优路径终点
                results = preEquivalentPath + s + ';\n';
            } else {//结果集包含所有最优路径
                preEquivalentPath += s + '->';
                for (let i in indicators[k][s].opt) {
                    results += optPath(k + 1, indicators[k][s].opt[i], preEquivalentPath);
                }
            }
    
            return results;
        }
    }

      

 二. Floyd算法

  Floyd算法又称为插点法,是利用动态规划思想求出它的每两点间的最短路径的算法。 Floyd算法时间复杂度为O(n^3),空间复杂度为O(n^2)。

  • 算法描述:把图用邻接矩阵G表示出来,如果Vi到Vj的边存在,则G[i,j]=该边的权,否则G[i,j]=无穷大;另外,如果Vi、Vj为同一点,则权为0。定义一个矩阵Path用来记录路径信息,Path[i,j]表示从Vi到Vj需要经过的点,初始时Path[i,j] = j。对于任一点Vk,取G[i,j] = min( G[i,j], G[i,k]+G[k,j] ),如果G[i,j]的值变小,则Path[i,j] = Path[i,k]

     

  • 状态转移方程
    G[i,j] = min{G[i,k] + G[k,j], G[i,j]}
    
    Path[i,j] = Path[i,k]

     

  • 适用范围
    1. 适用于APSP(All Pairs Shortest Paths,多源最短路径)
    2. 稠密图效果最佳
    3. 边权可正可负

     

  • Javascript实现算法
    
    /**
     * 示例:五个端点按照图的顺序构成一条路经,其5x5邻接矩阵如下:
     *      g = [
     *              [0,100,1,100,100],
     *              [100,0,100,100,4],
     *              [1,100,0,2,100],
     *              [100,100,2,0,3],
     *              [100,4,100,3,0],
     *          ]
     *      Floyd(g)
     *
     *      0-1(1):0->1;
     *      0-2(3):0->1->2;
     *      0-3(6):0->1->2->3;
     *      0-4(10):0->1->2->3->4;
     *      1-0(1):1->0;
     *      1-2(2):1->2;
     *      1-3(5):1->2->3;
     *      1-4(9):1->2->3->4;
     *      2-0(3):2->1->0;
     *      2-1(2):2->1;
     *      2-3(3):2->3;
     *      2-4(7):2->3->4;
     *      3-0(6):3->2->1->0;
     *      3-1(5):3->2->1;
     *      3-2(3):3->2;
     *      3-4(4):3->4;
     *      4-0(10):4->3->2->1->0;
     *      4-1(9):4->3->2->1;
     *      4-2(7):4->3->2;
     *      4-3(4):4->3;
     *
     * @param array g nxn邻接矩阵
     * @return string
     */
    
    function Floyd(g) {
        let n = g.length,//获取端点的数目
            paths = new Array(n);//最短路径初始化
        for (let i = 0; i < n; i++) {
            paths[i] = new Array(n);
            for (let j = 0; j < n; j++) {
                paths[i][j] = j;
            }
        }
    
        for (let k = 0; k < n; k++) {//求邻接矩阵的最短路径距离及最短路径集
            for (let i = 0; i < n; i++) {
                for (let j = 0; j < n; j++) {
                    if (g[i][k] + g[k][j] < g[i][j]) {
                        g[i][j] = g[i][k] + g[k][j];
                        paths[i][j] = paths[i][k];
                    }
                }
            }
        }
    
        let optPaths = '',
           post;
        for (let i = 0; i < n; i++) {//获取最短路径集
            for (let j = 0; j < n; j++) {
                if (i == j) {
                    continue;
                } else {
                    optPaths += i + '-' + j + '(' + g[i][j] + '):';
                    post = i;
                    while (post != j) {
                        optPaths += post + '->';
                        post = paths[post][j];
                    }
                    optPaths += j + ';\n';
                }
            }
        }
    
        return optPaths;
    }

     将“求邻接矩阵的最短路径距离及最短路径集”部分代码改成:

       for (let i = 0; i < n; i++) {//求邻接矩阵的最短路径距离及最短路径集
            for (let j = 0; j < n; j++) {
                for (let k = 0; k < n; k++) {
                    if (g[i][k] + g[k][j] < g[i][j]) {
                        g[i][j] = g[i][k] + g[k][j];
                        paths[i][j] = paths[i][k];
                    }
                }
            }
       }

    将出现意外结果如下:

    0-1(100):0->1;
    0-2(1):0->2;
    0-3(3):0->2->3;
    0-4(6):0->2->3->4;
    1-0(100):1->0;
    1-2(100):1->2;
    1-3(7):1->4->3;
    1-4(4):1->4;
    2-0(1):2->0;
    2-1(100):2->1;
    2-3(2):2->3;
    2-4(5):2->3->4;
    3-0(3):3->2->0;
    3-1(7):3->4->1;
    3-2(2):3->2;
    3-4(3):3->4;
    4-0(6):4->3->2->0;
    4-1(4):4->1;
    4-2(5):4->3->2;
    4-3(3):4->3;

     

     

三. Dijkstra算法

  Dijkstra(迪杰斯特拉)算法是典型的单源最短路径算法,用于计算一个节点到其他所有节点的最短路径,主要特点是以起始点为中心向外层层扩展,直到扩展到终点为止,该算法要求图中不存在负权边。 

  • 算法描述
    G = {V, E},V0为源点
    
    1. 初始时S = {V0},U = V - S = {其余顶点},同时S和U也维护各点Vi到V0的距离(下简称Vi的距离,V0的距离为0)。若不存在<V0,Vi>,则距离为无穷大;若存在<V0,Vi>,则Vi的距离为该边的权值
    
    2. 从U中选取一个距离最小的Vk,加入到S中
    
    3. U中余下顶点中若有以Vk作中间顶点,使得从V0到Vk再到Vi的距离之和小于U所标记的Vi的距离,则以前者重新标记U中Vi的距离
    
    4. 重复步骤2、3,直到S中包含所有顶点
  • 适用范围:
    1. 单源(同一个源点)
    2. 不存在负权边

     

  • Javascript实现算法
    
    /**
     * 示例:五个端点按照图的顺序构成一条路经,其5x5邻接矩阵如下:
     *      g = [
     *              [0,100,1,100,100],
     *              [100,0,100,100,4],
     *              [1,100,0,2,100],
     *              [100,100,2,0,3],
     *              [100,4,100,3,0],
     *          ]
     *      Dijkstra(g)
     *
     *
     * @param array g nxn邻接矩阵
     * @return string
     */
    
    function Dijkstra(g, infinity = 100) {
        let n = g.length,//获取顶点的数目
            infinityPlus1 = infinity + 1,//端点从g[0]数组中删除标识
            s = Array(n);//记录各端点相对于V0的最短路径值及前驱
        for (let i = 0; i < n; i++) {
            s[i] = [infinity, 0];
        }
    
      let mPath,
           k;   
        for (let i = 0; i < n; i++) {//求解k点最短路径,并从g[0]中删除它
            mPath = minPath();
            k = mPath[0];
            s[k][0] = mPath[1];
            g[0][k] = infinityPlus1;//标识该端点从g[0]数组中删除
    
            for (let j = 0; j < n; j++) {//查询并处理以k为前驱的j点
                if (g[0][j] != infinityPlus1 && s[k][0] + g[k][j] < g[0][j]) {
                    g[0][j] = s[k][0] + g[k][j];
                    s[j][1] = k;
                }
            }
        }
    
        return optPaths();//返回最短路径集
    
        function minPath() {//输出g[0]中的最小路径
            let path = [infinity, infinity];
            for (let i = 0; i < n; i++) {
                if (g[0][i] < path[1]) {
                    path[0] = i;
                    path[1] = g[0][i];
                }
            }
    
            return path;
        }
    
        function optPaths() {//输出最短路径集
            let paths = '',
               paths_i,
               preNode;
    
            for (let i = 1; i < n; i++) {
                paths_i = '';
                preNode = i;
                while (preNode) {
                    paths_i = '->' + preNode + paths_i;
                    preNode = s[preNode][1];
                }
                paths_i = '0' + paths_i;
                paths += '0->' + i + '(' + s[i][0] + '): ' + paths_i + ';\n';
            }
    
            return paths;
        }
    }

 

四. Bellman-Ford算法

  Bellman-Ford算法能在更普遍的情况下(存在负权边)解决单源点最短路径问题。对于给定的带权(有向或无向)图 G=(V,E), 其源点为S,加权函数 w是 边集 E 的映射。对图G运行Bellman - Ford算法的结果是一个布尔值,表明图中是否存在着一个从源点S可达的负权回路。若不存在这样的回路,算法将给出从源点S到图G的任意顶点Vi的最短路径D[Vi]。 

  • 算法描述
    1. 将除源点S外的所有顶点的最短距离估计值 D[Vi]—>+∞, D[S]—>0
    
    2. 反复对边集E中的每条边进行松弛操作,使得顶点集V中的每个顶点Vi的最短距离估计值逐步逼近其最短距离
    
    3. 判断边集E中的每一条边的两个端点是否收敛。如果存在未收敛的顶点,则算法返回false,表明问题无解;否则算法返回true,并且从源点可达的顶点Vi的最短距离保存在D[Vi]中

     

  • 适用范围
    1. 单源最短路径
    2. 有向图和无向图
    3. 边权可正可负
    4. 差分约束系统
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值