Shortest path in JavaScript

前端 未结 2 1202
旧巷少年郎
旧巷少年郎 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(' → '));
            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(' → '));
            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 + '
    '; } 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;
    }
    
    
    

提交回复
热议问题