zoukankan      html  css  js  c++  java
  • 求最短路径算法系列

    一 . 动态规划算法

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

    • 划分阶段

      

    • 求解最短路径: 

        

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

      

    • 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 + ':
      ' + optPath(1, i);
                  }
              } else if (k > k0) {//结果集包含最优路径终点
                  results = preEquivalentPath + s + ';
      ';
              } 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 + '; '; } } } 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 + '; '; } 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. 差分约束系统
     
  • 相关阅读:
    内存管理
    git学习思维导图
    shell编程 条件判断式----利用 case ..... esac 判断
    shell编程 条件判断式----利用 if .... then ----多重
    shell编程 Shell script 的默认变量($0, $1...)
    VxWorks实验六 基于优先级的抢占式调度及实验的源程序和实验步骤
    Linux下FTP用户的使用配置
    Linux 下 FTP虚拟用户的使用配置
    Linux 常用命令
    实验一 用户界面与Shell命令
  • 原文地址:https://www.cnblogs.com/XiongMaoMengNan/p/6754486.html
Copyright © 2011-2022 走看看