Shortest path in JavaScript

前端 未结 2 1198
旧巷少年郎
旧巷少年郎 2021-02-07 23:23

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 nam

相关标签:
2条回答
  • 2021-02-08 00:05

    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>

    0 讨论(0)
  • 2021-02-08 00:21

    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.

    0 讨论(0)
提交回复
热议问题