首发于前端算法
Floyd算法

Floyd算法

一个号称只有5行代码的算法, 由1978年图灵奖获得者、斯坦福大学计算机科学系教授罗伯特·弗洛伊德命名。该算法有于求一个带权有向图(Wighted Directed Graph)的任意两点的最短距离的算法,运用了动态规划的思想, 算法的时间复杂度为O(V^3),空间复杂度O(V^2)。


其核心思想是,在两个顶点之间插入一个或一个以上的中转点,比较经过与不经过中转点的距离哪个更短。同时,我们需要引入2个矩阵,一个邻接矩阵D,用来计算每个相邻点的距离,也就是我们的已知条件,第二个矩阵P,则用来表示中间点k的代数。比如说P中p[i,j]的值就是i与j两点的中间点代数。


我们在进行Floyd算法的时候,也要像Dijkstra算法一样,不停的更新这两个矩阵。当我们根据一点规律变化中间点k的时候,也要遍历所有的最小距离和中间点,若D[i,j]<D[i,k]+D[j,k],则矩阵D中的distance[i,j]需要改变成D[i,k]+D[j,k],矩阵P中的P[i,j]要改为k。在遍历掉所有点为中心之后,Floyd算法完成。


这个中转点的思想,我们可以想象现实中的自ekd旅行驾游问题,有的城市间的道路好走,比如存在高速公路,其需要的时间就越短,有的城市间只有原始的泥泞小道,行驶时就很耗时间。


比如A地直接到B地需要7小时


A地经由C地再到B地,则要1+4=5小时

A地经由C地再到D地再到B地,则要1+1+1小时。


换言之,这类似动态规则的背包问题,从A地到B地,每个中转点可以选择也可以不选择,这个逻辑就是对应代码中的松弛操作。要求出所有地方(顶点)之间的最短距离,就需要n^3次松弛操作(三重循环)

Floyd的核心5行代码:


 for(k=0;k<n;k++)//中转站0~k
        for(i=0;i<n;i++) //i为起点
            for(j=0;j<n;j++) //j为终点
                if(d[i][j]>d[i][k]+d[k][j])//松弛操作 
                     d[i][j]=d[i][k]+d[k][j]; 


需要注意的是,Floyd算法都是围绕顶点展开,因此其表示法只能选择相邻距阵。在相邻距阵中,我们默认所有顶点的权重是无限大,表示它们都不相邻,其实这在Floyd算法有点不对,因为顶点到它自身的距离应该为零,因此这个要改动一下。

其完整实现如下:

  class GraphByMatrix {
      constructor(vertices, isDirected = false) {
        var n = vertices.length;
        var matrix = [];
        //建立一个正方形
        for (var i = 0; i < n; i++) {
          matrix[i] = []
          for (var j = 0; j < n; j++) {
            matrix[i][j] = Infinity
          }
        }
        var map = {}
        for (var i = 0; i < vertices.length; i++) {
          map[vertices[i]] = i
        }
        this.matrix = matrix;     // 矩阵 
        this.vertices = vertices;   // 顶点数组(里面只能是索引值)
        this.map = map
        this.isDirected = false;
      }
      addEdge(a, b, weight) {
        var aIndex = this.map[a]
        var bIndex = this.map[b]
        if(aIndex === void 0 || bIndex === void 0 ){
            throw "请提前设置顶点数组"
        }
        this.matrix[aIndex][bIndex] = weight || 1
        if(!this.isDirected){
            this.matrix[bIndex][aIndex] = weight || 1
        }
      }
      toString(obj) {
        function addWhiteSpace(str, n, left) {
          if (str.length < n) {
            if (left) {
              return addWhiteSpace(' ' + str, n, false)
            } else {
              return addWhiteSpace(str + ' ', n, true)
            }
          }
          return str
        }
        obj = obj || this.matrix;
        return obj.map(function (row) {
          return row.map(function (el) {
            return addWhiteSpace(el + "", 8, true)
          }).join('  ')
        }).join("\n");
      }
      floyd() {
        var n = this.matrix.length, distance = [], path = []
        for (var i = 0; i < n; i++) {
          distance[i] = []
          path[i] = []
          for (var j = 0; j < n; j++) {
            //重点,自己到自己为零
            distance[i][j] = i === j ? 0 : this.matrix[i][j]
            path[i][j] = -1;
          }
        }
        for (var k = 0; k < n; k++) {
          for (var i = 0; i < n; i++) {
            for (var j = 0; j < n; j++) {
              if (distance[i][j] > distance[i][k] + distance[k][j]) {
                distance[i][j] = distance[i][k] + distance[k][j]
                path[i][j] = k
                if (i == j && this.dist[i][j] < 0) {//发现了负权环
                  return false;
                }
              }
            }
          }
        }
        console.log("邻接矩阵")
        console.log(this + "")
        console.log("最短路径")
        console.log(this.toString(distance))
        console.log("path矩阵")
        console.log(this.toString(path))
        this.printPath(distance, path, n)
      }
      genPath(path, i, j, s) {
        var k = path[i][j];
        if (k == -1) {
          return;
        }
        this.genPath(path, i, k, s);
        s.push(k)
        this.genPath(path, k, j, s);
      }
      printPath(distance, path, n) {
        var i, j;
        for (i = 0; i < n; i++) {
          for (j = 0; j < n; j++) {
            if (this.matrix[i][j] === Infinity && i == j) {
              continue
            }
            var s = this.vertices[i] + '到' + this.vertices[j] + "  最短距离为:" + distance[i][j] + '\n'
            s += 'path: '
            var stack = [i]
            this.genPath(path, i, j, stack)
            stack.push(j)
            s += stack.join('->')
            console.log(s)

          }
        }
      }
    }

    var g = new GraphByMatrix([0, 1, 2, 3, 4, 5, 6, 7, 8], true);
    g.addEdge(0, 1, 1)
    g.addEdge(0, 2, 5)
    g.addEdge(1, 2, 3)
    g.addEdge(1, 3, 7)
    g.addEdge(1, 4, 5)
    g.addEdge(2, 4, 1)
    g.addEdge(2, 5, 7)
    g.addEdge(3, 4, 2)
    g.addEdge(3, 6, 3)
    g.addEdge(4, 5, 3)
    g.addEdge(4, 6, 6)
    g.addEdge(4, 7, 9)
    g.addEdge(5, 7, 5)
    g.addEdge(6, 7, 2)
    g.addEdge(6, 8, 7)
    g.addEdge(7, 8, 4)
    g.floyd()

编辑于 2019-07-04

文章被以下专栏收录