2015-09-11 13 views
5

Ich habe seit Wochen nach einer Möglichkeit gesucht, kürzeste Pfade in JavaScript zu berechnen. Ich habe mit dem Buch Datenstrukturen und Algorithmen von Groner (treffend benannt) bei https://github.com/loiane/javascript-datastructures-algorithms/tree/master/chapter09 gespielt.Kürzester Pfad in JavaScript

Das Problem, das ich immer wieder finde, ist, dass der Code so angepasst ist, dass es fast unmöglich ist, die gewünschten Ergebnisse neu zu schreiben. Ich möchte den kürzesten Weg von einem gegebenen Eckpunkt zu einem anderen Knoten haben, anstatt, wie Groner es kodiert, nur die Liste von allem aus A. Ich möchte zum Beispiel den Weg von F nach B oder von C nach A.

Der vollständige Code ist hier: http://jsfiddle.net/8cn7e2x8/

Kann jemand helfen?

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); 
} 

Antwort

11

Beginnen wir mit der Bemerkung, dass Breitensuche (BFS) die kürzesten Pfade von einem gegebenen Quellknoten berechnet, wenn der Graph nicht gewichtet ist. Mit anderen Worten, wir betrachten die Länge eines Pfades als die Anzahl der Kanten im Pfad.

Hier ist eine einfache Möglichkeit, eine ungewichtete Graphen zu konstruieren:

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; 
} 

Bitte beachte, dass wir einen ungerichteten Graphen umgesetzt haben. Wie in den Inline-Kommentaren erwähnt, können Sie den Code ändern, um einen gerichteten Graphen zu erstellen, indem Sie vier Zeilen aus der addEdge-Funktion löschen.

Diese Implementierung von BFS würde auf einem gerichteten Graphen gleich gut funktionieren:

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 }); 
     } 
    }); 
    } 
} 

Um den kürzesten Weg zwischen zwei vorgegebenen Eckpunkten zu finden und die Scheitelpunkte entlang des Weges zeigen, haben wir den Vorgänger von jedem Scheitelpunkt erinnern wie wir die Grafik erkunden:

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) { 
      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); 
} 

der folgende Ausschnitt zeigt, diese Vorgänge in der Grafik, die Sie in Ihrer Frage geben. Zuerst finden wir die kürzesten Wege zu allen Knoten, die von A erreichbar sind. Dann finden wir den kürzesten Weg von B zu G und von G zu 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(' &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'); 
 
};
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>

+0

Ich fürchte, ich verstehe nicht, wie man diese Funktion benutzt: wenn ich etwas wie var 'code' versuche, starte = myVertices [1]; var end = myVertices [5]; bfs (Ende); Ich bekomme eine Fehlermeldung, dass "graph [u] nicht definiert ist." Ich möchte nur einen Anfangs- und Endpunkt eingeben und einen einigermaßen direkten Weg einschlagen können. Macht das Sinn? – Tyler330

+0

Vielen Dank für die erstaunliche Geschwindigkeit - wenn ich das schnell und genau codieren könnte, wäre ich ein glücklicherer Mensch. Das ist in etwa das, wonach ich suche: Nach Wochen, in denen ich A * versuchte, wurde mir klar, dass eigentlich ein einigermaßen direkter Weg besser funktionieren könnte, da dies Verkehrsstaus reduzieren würde. – Tyler330

+0

Ich gab Ihnen eine generische BFS-Implementierung, anstatt zu versuchen, mit der Maschinerie dieser Diagrammbibliothek zu interagieren. Nachdem ich mir die Bibliothek angeschaut habe, sehe ich, was Sie damit meinen, wie schwierig es ist, damit zu arbeiten. Ich denke, es ist unnötig kompliziert. Bitte sehen Sie meine überarbeitete Antwort. Ich habe eine übersichtliche Graphimplementierung hinzugefügt und eine neue Breitensuche geschrieben, die den kürzesten Pfad zwischen zwei gegebenen Knoten anzeigt. –

1

Lesen Ihrer Frage, ich kann es eine von zwei Arten lesen ... Entweder Sie versuchen, die Menge der Dinge zu reduzieren überprüft er, oder Sie versuchen, sich selbst zu erlauben, in Variablen zu übergeben Ändern Sie die Endpunkte. Ich werde das erstere annehmen und jemand anderen den letzteren Fall behandeln lassen.

Wenn Sie einen flüchtigen Blick auf das Problem werfen, sieht es so aus, als wären Sie auf das gestoßen, was in Comp Sci als "Das Problem des reisenden Verkäufers" bekannt ist. Es ist ein klassisches Problem in der Computerprogrammierung, das als logische Unmöglichkeit gilt und ein gutes Beispiel dafür ist, "dass es perfekt ist, der Feind des Guten zu sein".

Das Problem des klassischen Geschäftsreisenden ist, "Programmieren Sie einem Verkäufer die Möglichkeit, alle seine Zielstädte auf einer Karte in kürzester Zeit zu erreichen. Tun Sie dies, ohne jeden einzelnen möglichen Weg prüfen zu müssen." Die Sache ist, logische Art, dies zu tun, muss (noch) jemals entdeckt werden (es muss noch bewiesen werden, wenn es unmöglich oder möglich ist). Das heißt, wenn es nicht der kürzeste, sondern nur ein kürzerer Pfad sein muss, gibt es eine Reihe von Verknüpfungen, die verwendet werden können. Ein Beispiel besteht darin, nur eine Linie von Anfang bis Ende zu berechnen und dann die Abweichungen einzubringen, um sie mit den nächstgelegenen Vertices abzugleichen. Ein anderer besteht darin, die Pfade in Dreiecke aufzuteilen, die jeweils nur die Scheitelpunkte mit den nächsten zwei Scheitelpunkten verbinden. Anschließend verbinden Sie die Klumpen auf die gleiche Weise, bis alle Scheitelpunkte verbunden sind, und berechnen dann nur die möglichen Ausgangspfade aus diesen Teilmengen.

Keine dieser beiden Antworten garantiert Ihnen die beste Antwort, aber sie werden eine gute Antwort mit viel weniger Rechenzeit bieten, so dass Sie nicht jeden Pfad berechnen müssen, der von A und B und C kommt

+0

Per ersten Absatz, versuche ich, direkte Wege von einem Punkt zum anderen zu finden. Ich habe seit ungefähr drei Wochen über A * und Kürzel-Artikel geforscht, und ich habe keine gefunden, die eine gute Javascript-Antwort geben, um die Schritte im Weg von zum Beispiel A zu F in der Form "A" zu finden "," B "," E "," F ". Erstaunlich, wie viele Leute das Thema angehen, aber nicht an einem nützlichen Ort enden. Und ich habe viel Pseudo-Code gelesen, versuche aber immer noch genug zu lernen, um den Code in Javascript zu übersetzen. – Tyler330

Verwandte Themen