JavaScriptで最短経路を計算する方法を何週間も探していました。私はGroner(適切な名前)の本Data Structures and Algorithmsで遊んでいます https://github.com/loiane/ javascript-datastructures-algorithms/tree/master/chapter09 。
私が見つけ続ける問題は、コードが非常にカスタマイズされているため、目的の結果を生成するために書き直すことがほとんど不可能であるということです。 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);
}
グラフが重み付けされていない場合、幅優先探索(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
関数から4行を削除することで、コードを変更して有向グラフを作成できます。
この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 });
}
});
}
}
与えられた2つの頂点間の最短経路を見つけ、その経路に沿って頂点を表示するには、グラフを探索するときに各頂点の先行を覚えておく必要があります。
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(' → '));
return;
}
predecessor[v] = u;
queue.Push(v);
}
}
print('there is no path from ' + source + ' to ' + target);
}
次のスニペットは、質問で指定したグラフでのこれらの操作を示しています。まず、A
から到達可能なすべての頂点への最短経路を見つけます。次に、B
からG
へ、およびG
からA
への最短経路を見つけます。
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(' → '));
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');
};
body {
font-family: 'Source Code Pro', monospace;
font-size: 12px;
}
<link rel="stylesheet" type="text/css"
href="https://fonts.googleapis.com/css?family=Source+Code+Pro">
<div id="display"></id>
あなたの質問を読んで、私はそれを2つの方法のいずれかで読むことができます...あなたはそれがチェックするものの量を減らしようとしている、またはあなた自身がエンドポイントを変更するために変数を渡せるようにしようとしています。前者を想定し、他の誰かに後者のケースを処理させます。
問題をざっと見てみると、CompSciで「巡回セールスマン問題」として知られている問題に遭遇したようです。これは、論理的に不可能であると考えられているコンピュータプログラミングの古典的な問題であり、「完全に善の敵である」ことの良い例です。
古典的な巡回セールスマン問題は、「セールスマンが地図上のすべての目的地の都市に最短時間で到達する方法をプログラムすることです。可能なすべてのパスをチェックする必要なしにそうする」です。重要なのは、これを行う論理的な方法は(まだ)発見されていないということです(それが不可能または可能かどうかはまだ証明されていません)。とはいえ、最短である必要はなく、パスが短い場合は、いくつかのショートカットを使用できます。 1つの例は、最初から最後まで線を計算し、偏差を押し込んで最も近い頂点と一致させることです。もう1つは、パスを三角形に分割して、各頂点を次に近い2つの頂点にのみ接続し、すべての頂点が接続されるまで同じ方法でクランプを接続してから、それらのサブセットから開始する可能性のあるパスのみを計算することです。
これらの2つの答えはどちらも最良の答えを与えるとは限りませんが、必要な計算時間が大幅に短縮されて良い答えが得られるため、A、B、Cなどからのすべてのパスを計算する必要はありません。