17

I have been searching for weeks for a way to compute shortest paths in JavaScript. I have been playing with the book Data Structures and Algorithms by Groner (aptly named) at https://github.com/loiane/javascript-datastructures-algorithms/tree/master/chapter09.

The trouble I keep on finding is that the code is so customized that it is almost impossible to rewrite to produce the desired results. I would like to be able to get the shortest path from any given vertex to any other, rather than, as Groner codes it, just the list of everything from A. I would like to be able to get, for example, the path from F to B, or from C to A.

The full code is here: http://jsfiddle.net/8cn7e2x8/

Can anyone help?

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);
}
Michael Laszlo
  • 12,009
  • 2
  • 29
  • 47
Tyler330
  • 395
  • 2
  • 6
  • 15

2 Answers2

29

Let us begin by remarking that breadth-first search (BFS) computes the shortest paths from a given source vertex if the graph is unweighted. In other words, we consider the length of a path to be the number of edges in the path.

Here is an easy way to construct an unweighted graph:

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

Note that we have implemented an undirected graph. As mentioned in the inline comments, you can modify the code to construct a directed graph by deleting four lines from the addEdge function.

This implementation of BFS would work equally well on a directed graph:

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

To find the shortest path between two given vertices and display the vertices along the path, we have to remember the predecessor of each vertex as we explore the graph:

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

The following snippet demonstrates these operations on the graph that you gave in your question. First we find the shortest paths to all vertices reachable from A. Then we find the shortest path from B to G and from G to 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>
KimKha
  • 4,370
  • 1
  • 37
  • 45
Michael Laszlo
  • 12,009
  • 2
  • 29
  • 47
  • 1
    I am afraid I don't understand how to use this function, though: when I try something like var 'code' start = myVertices[1]; var end = myVertices[5]; bfs(end); I get an error message that "graph[u] is undefined." I would just like to be able to input a start and end point, and have it work out a reasonably direct path. Does this make sense? – Tyler330 Sep 11 '15 at 21:41
  • 1
    Thanks you for the amazing speed -- if I could code this quickly and accurately, I would be a happier person. This is roughly what I am looking for: after weeks trying A*, I realized that actually a reasonably direct path might work better, as it would reduce traffic-jams. – Tyler330 Sep 11 '15 at 21:43
  • 1
    I gave you a generic BFS implementation instead of trying to interface with the machinery of that graph library. After looking at the library, I see what you mean about the difficulty of working with it. I think it's needlessly complicated. Please see my revised answer. I've added a concise graph implementation and written a new breadth-first search that displays the shortest path between two given vertices. – Michael Laszlo Sep 12 '15 at 03:47
  • 1
    Wonderful! This is much clearer than the code I was working with, and makes much more sense. I will study this closely and try to understand where I was going wrong. Thank you! – Tyler330 Sep 12 '15 at 13:21
3

Reading your question, I can read it one of two ways... Either you're trying to reduce the amount of things it checks, or you're trying to allow yourself to pass in variables to change end points. I'm going to assume the former and let someone else handle the latter case.

Giving a cursory glance over the problem, it looks like you've come across what is known in Comp Sci as "The traveling salesman problem." It's a classical problem in computer programming that's considered a logical impossibility, and is a good example of "perfect being the enemy of the good."

The classical travelling salesman problem is this, "Program a way for a salesman to reach all of his destination cities on a map in the shortest time. Do so without having to check every single possible path." The thing is, logical way to do this has (yet) to ever be discovered (it's yet to be proved if it's impossible or possible). That said, if it doesn't have to be THE shortest, but just a shorter path, there's a number of shortcuts that can be taken. One example is just calculating a line from start to finish, and then push in the deviations to match up with closest vertices. Another is to break the paths into triangles that connect each vertices only to the next closest two vertices and then connect clumps the same way until all vertices are connected and then only calculate your starting potential paths from those subsets.

Neither of those two answers are guaranteed to give you the best answer, but they will provide a good answer with a lot less computational time required so you don't have to calculate every path coming from A and B and C etc. etc.

lilHar
  • 1,735
  • 3
  • 21
  • 35
  • Per the first paragraph, I am trying to find direct paths from one point to another. I have been poring over A* and shortest-path articles for about three weeks now, and I haven't found any that give a good javascript answer for finding the steps in the path from, say, A to F in the form "A", "B","E","F". Amazing how many people tackle the topic, but don't end up in a useful place. And I have read a lot of pseudo-code, but am still trying to learn enough to translate the code into javascript. – Tyler330 Sep 11 '15 at 21:47