JavaScript中的最短路径

时间:2015-09-11 15:28:39

标签: javascript shortest-path breadth-first-search

我一直在寻找一种计算JavaScript中最短路径的方法。我一直在玩https://github.com/loiane/javascript-datastructures-algorithms/tree/master/chapter09的Groner(适当命名)的书 Data Structures and Algorithms

我一直在寻找的问题是代码是如此定制的,几乎不可能重写以产生所需的结果。我希望能够获得从任何给定顶点到任何其他顶点的最短路径,而不是像Groner编码那样,只是来自A的所有内容的列表。我希望能够获得,例如,路径来自F到B,或从C到A.

完整代码在此处:http://jsfiddle.net/8cn7e2x8/

有人可以帮忙吗?

var graph = new Graph();
var myVertices = ['A','B','C','D','E','F'];
for (var i=0; i<myVertices.length; i++) {
    graph.addVertex(myVertices[i]);
}
graph.addEdge('A', 'B');
graph.addEdge('B', 'C');
graph.addEdge('B', 'E');
graph.addEdge('C', 'D');
graph.addEdge('C', 'E');
graph.addEdge('C', 'G');
graph.addEdge('D', 'E');
graph.addEdge('E', 'F');

graph.dfs();

console.log('********* sortest path - BFS ***********');
var shortestPathA = graph.BFS(myVertices[0]);

//from A to all other vertices
var fromVertex = myVertices[0];

for (i = 1; i < myVertices.length; i++) {
    var toVertex = myVertices[i],
    path = new Stack();
    for (var v = toVertex; v !== fromVertex; v = shortestPathA.predecessors[v]) {
        path.push(v);
    }
    path.push(fromVertex);
    var s = path.pop();
    while (!path.isEmpty()) {
        s += ' - ' + path.pop();
    }
    console.log(s);
}

2 个答案:

答案 0 :(得分:14)

让我们首先评论广度优先搜索(BFS)如果图表未加权,则计算来自给定源顶点的最短路径。换句话说,我们将路径的长度视为路径中的边数。

以下是构建未加权图表的简便方法:

function Graph() {
  var neighbors = this.neighbors = {}; // Key = vertex, value = array of neighbors.

  this.addEdge = function (u, v) {
    if (neighbors[u] === undefined) {  // Add the edge u -> v.
      neighbors[u] = [];
    }
    neighbors[u].push(v);
    if (neighbors[v] === undefined) {  // Also add the edge v -> u so as
      neighbors[v] = [];               // to implement an undirected graph.
    }                                  // For a directed graph, delete
    neighbors[v].push(u);              // these four lines.
  };

  return this;
}

请注意,我们已实现了无向图。如内联注释中所述,您可以通过删除addEdge函数中的四行来修改代码以构造有向图。

BFS的这种实现在有向图上同样有效:

function bfs(graph, source) {
  var queue = [ { vertex: source, count: 0 } ],
      visited = { source: true },
      tail = 0;
  while (tail < queue.length) {
    var u = queue[tail].vertex,
        count = queue[tail++].count;  // Pop a vertex off the queue.
    print('distance from ' + source + ' to ' + u + ': ' + count);
    graph.neighbors[u].forEach(function (v) {
      if (!visited[v]) {
        visited[v] = true;
        queue.push({ vertex: v, count: count + 1 });
      }
    });
  }
}

要找到两个给定顶点之间的最短路径并沿路径显示顶点,我们必须在探索图形时记住每个顶点的前驱:

function shortestPath(graph, source, target) {
  if (source == target) {   // Delete these four lines if
    print(source);          // you want to look for a cycle
    return;                 // when the source is equal to
  }                         // the target.
  var queue = [ source ],
      visited = { source: true },
      predecessor = {},
      tail = 0;
  while (tail < queue.length) {
    var u = queue[tail++],  // Pop a vertex off the queue.
        neighbors = graph.neighbors[u];
    for (var i = 0; i < neighbors.length; ++i) {
      var v = neighbors[i];
      if (visited[v]) {
        continue;
      }
      visited[v] = true;
      if (v === target) {   // Check if the path is complete.
        var path = [ v ];   // If so, backtrack through the path.
        while (u !== source) {
          path.push(u);
          u = predecessor[u];          
        }
        path.push(u);
        path.reverse();
        print(path.join(' &rarr; '));
        return;
      }
      predecessor[v] = u;
      queue.push(v);
    }
  }
  print('there is no path from ' + source + ' to ' + target);
}

以下代码段在您在问题中提供的图表上演示了这些操作。首先,我们找到可以从A到达的所有顶点的最短路径。然后我们找到从BG以及从GA的最短路径。

&#13;
&#13;
function Graph() {
  var neighbors = this.neighbors = {}; // Key = vertex, value = array of neighbors.

  this.addEdge = function (u, v) {
    if (neighbors[u] === undefined) {  // Add the edge u -> v.
      neighbors[u] = [];
    }
    neighbors[u].push(v);
    if (neighbors[v] === undefined) {  // Also add the edge v -> u in order
      neighbors[v] = [];               // to implement an undirected graph.
    }                                  // For a directed graph, delete
    neighbors[v].push(u);              // these four lines.
  };

  return this;
}

function bfs(graph, source) {
  var queue = [ { vertex: source, count: 0 } ],
      visited = { source: true },
      tail = 0;
  while (tail < queue.length) {
    var u = queue[tail].vertex,
        count = queue[tail++].count;  // Pop a vertex off the queue.
    print('distance from ' + source + ' to ' + u + ': ' + count);
    graph.neighbors[u].forEach(function (v) {
      if (!visited[v]) {
        visited[v] = true;
        queue.push({ vertex: v, count: count + 1 });
      }
    });
  }
}

function shortestPath(graph, source, target) {
  if (source == target) {   // Delete these four lines if
    print(source);          // you want to look for a cycle
    return;                 // when the source is equal to
  }                         // the target.
  var queue = [ source ],
      visited = { source: true },
      predecessor = {},
      tail = 0;
  while (tail < queue.length) {
    var u = queue[tail++],  // Pop a vertex off the queue.
        neighbors = graph.neighbors[u];
    for (var i = 0; i < neighbors.length; ++i) {
      var v = neighbors[i];
      if (visited[v]) {
        continue;
      }
      visited[v] = true;
      if (v === target) {   // Check if the path is complete.
        var path = [ v ];   // If so, backtrack through the path.
        while (u !== source) {
          path.push(u);
          u = predecessor[u];
        }
        path.push(u);
        path.reverse();
        print(path.join(' &rarr; '));
        return;
      }
      predecessor[v] = u;
      queue.push(v);
    }
  }
  print('there is no path from ' + source + ' to ' + target);
}

function print(s) {  // A quick and dirty way to display output.
  s = s || '';
  document.getElementById('display').innerHTML += s + '<br>';
}

window.onload = function () {
  var graph = new Graph();
  graph.addEdge('A', 'B');
  graph.addEdge('B', 'C');
  graph.addEdge('B', 'E');
  graph.addEdge('C', 'D');
  graph.addEdge('C', 'E');
  graph.addEdge('C', 'G');
  graph.addEdge('D', 'E');
  graph.addEdge('E', 'F');

  bfs(graph, 'A');
  print();
  shortestPath(graph, 'B', 'G');
  print();
  shortestPath(graph, 'G', 'A');
};
&#13;
body { 
  font-family: 'Source Code Pro', monospace;
  font-size: 12px;
}
&#13;
<link rel="stylesheet" type="text/css"
 href="https://fonts.googleapis.com/css?family=Source+Code+Pro">

<div id="display"></id>
&#13;
&#13;
&#13;

答案 1 :(得分:2)

阅读你的问题,我可以通过以下两种方式阅读... 要么你试图减少它检查的东西数量,要么你试图让自己传递变量来改变终点。我将承担前者并让其他人处理后一种情况。

粗略地看一眼这个问题,看起来你已经看到Comp Sci所知道的&#34;旅行商问题。&#34;这是计算机编程中的一个经典问题,被认为是逻辑上的不可能性,并且是完美成为善的敌人的一个很好的例子。&#34;

古典旅行商问题就是这样,&#34;为销售人员在最短的时间内在地图上覆盖所有目的地城市的方式。这样做无需检查每一条可能的路径。&#34;事情是,这样做的逻辑方式(尚未)被发现(如果它不可能或不可能,它还有待证明)。也就是说,如果它不是最短的,而只是一条较短的路径,那么可以采取一些快捷方式。一个例子是从开始到结束计算一条线,然后推入偏差以匹配最近的顶点。另一种方法是将路径分成三角形,将每个顶点仅连接到下一个最接近的两个顶点,然后以相同的方式连接块,直到所有顶点都连接起来,然后只计算这些子集的起始潜在路径。

这两个答案都不能保证给你最好的答案,但它们会提供一个很好的答案,所需的计算时间要少得多,所以你不必计算来自A,B和C的每条路径。等等。