图 DFS 方法调用堆栈展开混乱

问题描述

损坏的方法hasPathDFSbroken 工作版本:hasPathDFS

工作版本添加一个人为的参数以使其工作,我宁愿避免。

我试图理解为什么在损坏的版本中,当调用堆栈开始在 LIM 作为 currNode 展开并且它作为 currentNode 返回到 MEX 时,为什么它不恢复 MEX 邻居上未完成的 for 循环?

任何帮助将不胜感激。谢谢!

const airports = "PHX BKK OKC JFK LAX MEX EZE HEL LOS LAP LIM".split(" ");

const routes = [
  ["PHX","LAX"],["PHX","JFK"],["JFK","OKC"],"HEL"],"LOS"],["MEX","BKK"],"LIM"],"EZE"],["LIM",];

class Node {
  constructor(data) {
    this.data = data;
    this.neighbors = new Map();
  }

  addNeighbor(node) {
    this.neighbors.set(node.data,node);
    return this.neighbors.size;
  }

  getNeighbors() {
    return this.neighbors;
  }
}

class Graph {
  constructor(edgeDirection = Graph.UNDIRECTED) {
    this.nodes = new Map();
    this.edgeDirection = edgeDirection;
  }

  /* ???
  When the call stack starts unwinding at LIM as currNode and it gets back to MEX 
  as currNode,why doesn't the unfinished for loop resume over MEX's neighbors? 
  */
  hasPathDFSbroken(
    start,destination,currNode = this.nodes.get(start),visited = new Map()
  ) {
    if (!currNode) {
      return false;
    }

    visited.set(currNode.data,1);

    console.log(
      "currNode:",currNode.data,"| neighbors:",[...currNode.getNeighbors().keys()],"| visited:",[...visited.keys()]
    );

    for (const [neighborData,neighborNode] of currNode
      .getNeighbors()
      .entries()) {
      console.log("currNeighbor:",neighborData);

      if (neighborData === destination) {
        return true;
      }

      if (!visited.has(neighborData)) {
        return this.hasPathDFSbroken(start,neighborNode,visited);
      }
    }
    return false;
  }

  // Works but uses contrived found param for pass by ref.
  hasPathDFS(
    start,visited = new Map(),found = { res: false }
  ) {
    if (!currNode) {
      return false;
    }

    visited.set(currNode.data,neighborData);

      if (neighborData === destination) {
        return (found.res = true);
      }

      if (!visited.has(neighborData)) {
        this.hasPathDFS(start,visited,found);
      }
    }
    return found.res;
  }

  addVertex(data) {
    if (this.nodes.has(data)) {
      return this.nodes.get(data);
    }
    const vertex = new Node(data);
    this.nodes.set(data,vertex);
    return vertex;
  }

  addEdge(source,destination) {
    const sourceNode = this.addVertex(source);
    const destinationNode = this.addVertex(destination);

    sourceNode.addNeighbor(destinationNode);

    if (this.edgeDirection === Graph.UNDIRECTED) {
      destinationNode.addNeighbor(sourceNode);
    }
    return [sourceNode,destinationNode];
  }

  addEdges(edges) {
    edges.forEach(([source,destination]) => this.addEdge(source,destination));
    return this;
  }

  print() {
    let str = [...this.nodes.values()]
      .map(
        (node) =>
          `${node.data} => ${[...node.getNeighbors().keys()].join(",")}`
      )
      .join("\n");

    str = `${"_".repeat(100)}\n${str}\n${"_".repeat(100)}`;
    console.log(str);
    return str;
  }
}

Graph.UNDIRECTED = Symbol("directed graph"); // two-way edges
Graph.DIRECTED = Symbol("undirected graph"); // one-way edges

const flightPaths = new Graph().addEdges(routes);
flightPaths.print();

console.log(flightPaths.hasPathDFSbroken("HEL","EZE"));
// console.log(flightPaths.hasPathDFS("HEL","EZE")); // working ver.

解决方法

为什么未完成的 for 循环不会在 MEX 的邻居上恢复?

因为您在循环中的 return 语句会立即中断循环和函数。

相反,如果递归调用没有找到目的地,您需要查看返回值并继续循环:

hasPathDFS(start,destination,currNode = this.nodes.get(start),visited = new Map()) {
  if (!currNode) {
    return false;
  }

  visited.set(currNode.data,1);

  for (const [neighborData,neighborNode] of currNode.getNeighbors()) {
    if (neighborData === destination) {
      return true;
    }

    if (!visited.has(neighborData)) {
      const found = this.hasPathDFSBroken(start,neighborNode,visited);
      if (found) return true;
//    ^^^^^^^^^^^^^^^^^^^^^^
      // else continue
    }
  }
  return false;
}

如果您尝试构建一个返回路径的 DFS,而不只是返回目标是否可达的布尔值,则该模式应该变得更加明显。

顺便说一句,我建议对 Set 使用 visited,而不是在循环内部检查 neighborData === destination,而是在循环外部进行基本情况比较 curreNode.data === destination循环(实际上,这是一个错误,您的版本不适用于搜索从节点到自身的路径)。