【JS】Floyed 求解任意两点之间的最短路径

来源:13-2 有向图算法

Potter520

2023-05-26

老师只讲了求解利用Floyed求解任意两点的最短路径值,却没有求解任意两点最短路径的具体路径是什么。通过自己的思考实现了,在此分享给大家(提出想着加个pre发现是不行的,因为pre的状态会被不停的更新,导致求出来的结果就错了)

解决思路:
1.假定:起始点为s,终止点为e
2.从起始点s,确定最短距离的点假如为t,那么就存在dis[s][t] == g.getWeight(s,t) + dis[t][e]
3.然后就s=t,再从剩下的s-e 依次求解,继续从第1步开始循环执行,直到找不到最短距离的t点,退出循环。

简化理解:从一个路径中不停的确定第一个点,然后再从剩余的路径中再确认一个点,最终就把起始点s、中间点、终点e,拼接起来就是任意两点的最短路径了。老师我这思路没有错吧?

class Floyed {
  constructor(g) {
    this._g = g;
    this._hasNegativeCycle = false;
    this.dis = Array(g.V())
      .fill(Number.MAX_SAFE_INTEGER)
      .map(() => Array(g.V()).fill(Number.MAX_SAFE_INTEGER));

    this.next = Array(g.V())
      .fill(-1)
      .map(() => Array(g.V()).fill(-1));

    for (let v = 0; v < g.V(); v++) {
      this.dis[v][v] = 0;
      for (let w of g.adj(v)) {
        this.dis[v][w] = g.getWeight(v, w);
        //! next[v][w]表示:v->w这条边的终点(初始:就是v->w 直连边,终点就是w)
        this.next[v][w] = w;
      }
    }

    //! 计算任意两点直接的最短距离
    for (let t = 0; t < g.V(); t++) {
      for (let v = 0; v < g.V(); v++) {
        for (let w = 0; w < g.V(); w++) {
          if (this.dis[v][t] != Number.MAX_SAFE_INTEGER && this.dis[t][w] != Number.MAX_SAFE_INTEGER && this.dis[v][t] + this.dis[t][w] < this.dis[v][w]) {
            this.dis[v][w] = this.dis[v][t] + this.dis[t][w];
            //! 说明:能找到一个中间点t,能更近的抵达w点。那么就更新next[v][w]的值
            //! 其实就是更新这条边的终点,让其通过next[v][t]这条边(终点)更近的边抵达终点w
            this.next[v][w] = this.next[v][t];
          }
        }
      }
    }

    for (let v = 0; v < g.V(); v++) {
      if (this.dis[v][v] < 0) {
        this._hasNegativeCycle = true;
        break;
      }
    }
  }

  hasNegativeCycle() {
    return this._hasNegativeCycle;
  }

  disTo(v, w) {
    this._g.validateVertex(v);
    this._g.validateVertex(w);
    return this.dis[v][w];
  }

  isConnectTo(v, w) {
    return this.dis[v][w] != Number.MAX_SAFE_INTEGER;
  }

  getLocation(k) {
    let s = (k / this._g.V()) | 0;
    let e = k % this._g.V();
    return { s, e };
  }

  pathTo1(v, w) {
    if (!this.isConnectTo(v, w)) {
      return [];
    }

    //! 自己到自己,直接返回
    if (v == w) {
      return [];
    }

    let s = v;
    let e = w;
    let path = [];
    path.push(s);

    while (true) {
      //! 是否能找到确定的第一个最短点
      let isCanFindMidPoint = false;
      for (let t = 0; t < this._g.V(); t++) {
        if (t == s || t == e) {
          continue;
        }
        //! 确定一个最短点 + 最短点至终点的最短距离
        if (this._g.hasEdge(s, t) && this._g.hasEdge(t, e) && this.dis[s][e] === this._g.getWeight(s, t) + this.dis[t][w]) {
          path.push(t);
          s = t;
          isCanFindMidPoint = true;
          break;
        }
      }

      if (!isCanFindMidPoint) {
        break;
      }
    }

    path.push(e);
    return path;
  }

  pathTo2(v, w) {
    if (!this.isConnectTo(v, w)) {
      return [];
    }

    //! 自己到自己,直接返回
    if (v == w) {
      return [];
    }

    let s = v;
    let e = w;
    let path = [];
    path.push(s);

    while (true) {
      //! 是否能找到确定的第一个最短点
      let isCanFindMidPoint = false;
      for (const t of this._g.adj(s)) {
        if (this._g.hasEdge(s, t) && this._g.hasEdge(t, e) && this.dis[s][e] === this._g.getWeight(s, t) + this.dis[t][w]) {
          path.push(t);
          s = t;
          isCanFindMidPoint = true;
          break;
        }
      }

      if (!isCanFindMidPoint) {
        break;
      }
    }

    path.push(e);
    return path;
  }

  /* 参考实现:https://www.geeksforgeeks.org/finding-shortest-path-between-any-two-nodes-using-floyd-warshall-algorithm/ */
  pathTo(v, w) {
    if (!this.isConnectTo(v, w)) {
      return [];
    }

    //! 自己到自己,直接返回
    if (v == w) {
      return [];
    }

    let path = [];
    path.push(v);

    while (v != w) {
      v = this.next[v][w];
      path.push(v);
    }

    return path;
  }
}

图片描述

测试代码

 let g = new WeightedGraph(resolve(__dirname, "assets/g.txt"));
  let floyed = new Floyed(g);
  if (!floyed.hasNegativeCycle()) {
    for (let v = 0; v < g.V(); v++) {
      for (let w = 0; w < g.V(); w++) {
        console.log(`${v}-${w}:`, floyed.disTo(v, w));
        console.log(`${v}-${w} path:`, floyed.pathTo(v, w));
      }
      console.log("-------------------------------");
    }
  } else {
    console.log("graph has negative cycle !");
  }

测试结果:

0-0: 0
0-0 path: []
0-1: 3
0-1 path: [ 0, 2, 1 ]
0-2: 2
0-2 path: [ 0, 2 ]
0-3: 5
0-3 path: [ 0, 2, 1, 3 ]
0-4: 6
0-4 path: [ 0, 2, 1, 3, 4 ]
-------------------------------
1-0: 3
1-0 path: [ 1, 2, 0 ]
1-1: 0
1-1 path: []
1-2: 1
1-2 path: [ 1, 2 ]
1-3: 2
1-3 path: [ 1, 3 ]
1-4: 3
1-4 path: [ 1, 3, 4 ]
-------------------------------
2-0: 2
2-0 path: [ 2, 0 ]
2-1: 1
2-1 path: [ 2, 1 ]
2-2: 0
2-2 path: []
2-3: 3
2-3 path: [ 2, 1, 3 ]
2-4: 4
2-4 path: [ 2, 1, 3, 4 ]
-------------------------------
3-0: 5
3-0 path: [ 3, 1, 2, 0 ]
3-1: 2
3-1 path: [ 3, 1 ]
3-2: 3
3-2 path: [ 3, 1, 2 ]
3-3: 0
3-3 path: []
3-4: 1
3-4 path: [ 3, 4 ]
-------------------------------
4-0: 6
4-0 path: [ 4, 1, 2, 0 ]
4-1: 3
4-1 path: [ 4, 3, 1 ]
4-2: 4
4-2 path: [ 4, 1, 2 ]
4-3: 1
4-3 path: [ 4, 3 ]
4-4: 0
4-4 path: []
-------------------------------
写回答

1回答

liuyubobobo

2023-05-27

具体代码我没有做验证,简单看了一下代码的逻辑思路,逻辑思路是 ok 的。


但是,找到这个路径的复杂度是 O(V^2) 或者 O(V + E) 的,因为从 s 开始,要遍历和 s 相邻的每一条边,来确定下一个点,到了下一个点,又要遍历每一条相邻的边,以此类推,最坏情况要遍历每一个点的每一条边(也就是遍历整张图。)


floyd 可以自求最短路径长度的时候做信息的保存,以用 O(n) 的时间获得具体路径。比如可以参考这篇文章:https://www.geeksforgeeks.org/finding-shortest-path-between-any-two-nodes-using-floyd-warshall-algorithm/


继续加油!:)

1
2
liuyubobobo
回复
Potter520
大赞!继续加油!:)
2023-05-30
共2条回复

玩转算法系列--图论精讲(Java版)

30+小时系统学习,bobo带你克服被图论支配的恐惧

1591 学习 · 324 问题

查看课程