JavaScript 实现最短路径算法

在求两点之间的最短距离最常用的算法:Dijkstra 算法 和 Floyd-Warshall 算法。

1、Dijkstra 算法

解决单源有向图最短路径问题,时间复杂度为 O(n2),n为顶点个数,如果是从其他顶点开始,那么在原有算法的基础上再来一次循环,此时的时间复杂度为O(n3)。
特点是以起始点为中心向外层层扩展(广度优先搜索思想),直到扩展到终点为止。

基本思想

通过 Dijkstra 计算图G 中的最短路径时,需要指定起点 s (即从顶点 s 开始计算)。
此外,引进两个集合 S 和 U。S 的作用是记录已求出最短路径的顶点(以及相应的最短路径长度),而 U 则是记录还未求出最短路径的顶点(以及该顶点到起点 s 的距离)。
初始时,S 中只有起点 s;U中是除s之外的顶点,并且U中顶点的路径是"起点 s 到该顶点的路径"。然后,从U中找出路径最短的顶点,并将其加入到 S 中;接着,更新 U 中的顶点和顶点对应的路径。 然后,再从U中找出路径最短的顶点,并将其加入到 S 中;接着,更新 U 中的顶点和顶点对应的路径。 ... 重复该操作,直到遍历完所有顶点。

Dijkstra 算法 图解

代码实现:

const INF = Number.MAX_SAFE_INTEGER;
// 查找最近的点
const minDistance = (dist, visited) => {
  let min = INF;
  let minIndex = -1;
  for (let v = 0; v < dist.length; v++) {
    if (visited[v] === false && dist[v] <= min) {
      min = dist[v];
      minIndex = v;
    }
  }
  return minIndex;
};
/** 
 * @param {图:邻接矩阵} graph 
 * @param {顶点索引} src 
 */
export const dijkstra = (graph, src) => {
  const dist = [];
  // 用于标识顶点 src 至其他顶点的距离是否确定
  const visited = [];
  const { length } = graph; 
  for (let i = 0; i < length; i++) {
    dist[i] = INF;
    visited[i] = false;
  }
  dist[src] = 0;
  for (let i = 0; i < length - 1; i++) {
    const u = minDistance(dist, visited);
    // 标识顶点 src 到此顶点的距离已经确认
    visited[u] = true;
    for (let v = 0; v < length; v++) {
      if (!visited[v] && graph[u][v] !== 0 && dist[u] !== INF && dist[u] + graph[u][v] < dist[v]) {
        // 更新 dist
        dist[v] = dist[u] + graph[u][v];
      }
    }
  }
  return dist;
};

// test 
const graph = [
    [0, 2, 4, 0, 0, 0],
    [0, 0, 2, 4, 2, 0],
    [0, 0, 0, 0, 3, 0],
    [0, 0, 0, 0, 0, 2],
    [0, 0, 0, 3, 0, 2],
    [0, 0, 0, 0, 0, 0]
];

const dist = dijkstra(graph, 0);
for (i = 0; i < dist.length; i++) {
    console.log(i + '\t\t' + dist[i]);
}
// result
0       0
1       2
2       4
3       6
4       4
5       6
2、Floyd-Warshall 算法

Floyd-Warshall 算法是一个经典的动态规划算法。
解决求所有顶点间的最短路径问题,时间复杂度为O(n3),n为顶点数。
可以正确处理有向图或负权的最短路径问题。

基本思想

要求任意节点 i 到任意节点 j 的最短路径,假设 Dis(i,j) 为节点 u 到节点 v 的最短路径的距离,对于每一个节点 k,我们检查 Dis(i,k) + Dis(k,j) < Dis(i,j) 是否成立,如果成立,证明从 i 到 k 再到 j 的路径比 i 直接到 j 的路径短,我们便设置Dis(i,j) = Dis(i,k) + Dis(k,j),这样一来,当我们遍历完所有节点 k,Dis(i,j)中记录的便是 i 到 j 的最短路径的距离。

Floyd-Warshall 算法图解

代码实现:

export const floydWarshall = graph => {
  const dist = [];
  const { length } = graph;
  // 初始化邻接矩阵
  for (let i = 0; i < length; i++) {
    dist[i] = [];
    for (let j = 0; j < length; j++) {
      if (i === j) {
        dist[i][j] = 0;
      } else if (!isFinite(graph[i][j])) {
        dist[i][j] = Infinity;
      } else {
        dist[i][j] = graph[i][j];
      }
    }
  }
  for (let k = 0; k < length; k++) {
    for (let i = 0; i < length; i++) {
      for (let j = 0; j < length; j++) {
        // 如果经过下标为 k 顶点路径比原两点间路径更短,当前两点间权值设为更小的一个
        if (dist[i][k] + dist[k][j] < dist[i][j]) {
          dist[i][j] = dist[i][k] + dist[k][j];
        }
      }
    }
  }
  return dist;
};

// test
const INF = Infinity;
const graph = [
  [INF, 2, 4, INF, INF, INF],
  [INF, INF, 2, 4, 2, INF],
  [INF, INF, INF, INF, 3, INF],
  [INF, INF, INF, INF, INF, 2],
  [INF, INF, INF, 3, INF, 2],
  [INF, INF, INF, INF, INF, INF]
];

dist = floydWarshall(graph);

let s = '';
for (let i = 0; i < dist.length; ++i) {
  s = '';
  for (var j = 0; j < dist.length; ++j) {
    if (dist[i][j] === INF) s += 'INF ';
    else s += dist[i][j] + '   ';
  }
  console.log(s);
}
// result
0   2   4   6   4   6   
INF 0   2   4   2   4   
INF INF 0   6   3   5   
INF INF INF 0   INF 2   
INF INF INF 3   0   2   
INF INF INF INF INF 0

参考:
单源最短路径演示

你可能感兴趣的:(JavaScript 实现最短路径算法)