【问题标题】:Graph DFS method call stack unwinding confusion图 DFS 方法调用堆栈展开混乱
【发布时间】:2021-05-24 01:30:14
【问题描述】:

破解方法:hasPathDFSBroken 工作版本:hasPathDFS

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

我试图理解为什么在损坏的版本中,当调用堆栈作为 currNode 在 LIM 开始展开并作为 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"],
  ["JFK", "HEL"],
  ["JFK", "LOS"],
  ["MEX", "LAX"],
  ["MEX", "BKK"],
  ["MEX", "LIM"],
  ["MEX", "EZE"],
  ["LIM", "BKK"],
];

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, destination, neighborNode, visited);
      }
    }
    return false;
  }

  // Works but uses contrived found param for pass by ref.
  hasPathDFS(
    start,
    destination,
    currNode = this.nodes.get(start),
    visited = new Map(),
    found = { res: false }
  ) {
    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 (found.res = true);
      }

      if (!visited.has(neighborData)) {
        this.hasPathDFS(start, destination, neighborNode, 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.

【问题讨论】:

  • 请在问题中包含相关代码,不要只链接到外部代码。如果代码太大而无法发布,那么它肯定太大而不能成为一个好问题,所以请尝试创建一个minimal reproducible example

标签: javascript node.js recursion graph depth-first-search


【解决方案1】:

为什么未完成的 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, destination, neighborNode, visited);
      if (found) return true;
//    ^^^^^^^^^^^^^^^^^^^^^^
      // else continue
    }
  }
  return false;
}

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

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

【讨论】:

  • 谢谢!你推荐Set 是因为它比Map 快吗?另外,如果你想增加重量,你会把重量放在哪里。由于同一个节点可以有多个具有不同权重的边,我会认为您不能将权重添加到节点本身,那么最好将节点包装在一个对象中以在外部对象上添加权重吗?跨度>
猜你喜欢
  • 1970-01-01
  • 2018-04-01
  • 2014-04-17
  • 1970-01-01
  • 2011-12-13
  • 2016-05-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多