图DFS方法调用栈展开混乱
Graph DFS method call stack unwinding confusion
破解方法:hasPathDFSBroken
工作版本:hasPathDFS
工作版本添加了一个人为的参数以使其工作,我宁愿避免。
我试图理解为什么在损坏的版本中,当调用堆栈作为 currNode 在 LIM 开始展开并返回到 MEX 作为 currentNode 时,为什么它不在 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.
why doesn't the unfinished for loop resume over MEX's neighbors?
因为循环中的 return
语句会立即从循环和函数中中断。
相反,如果递归调用没有找到目的地,您需要查看 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;
}
如果您尝试构建一个 return 路径的 DFS,而不仅仅是目的地是否可达的布尔值,模式应该会变得更加明显。
顺便说一句,我建议对 visited
使用 Set
,与其在循环内检查 neighborData === destination
,不如在循环外进行基本情况比较 curreNode.data === destination
循环(实际上,这是一个错误,您的版本不适用于搜索从节点到自身的路径)。
破解方法:hasPathDFSBroken
工作版本:hasPathDFS
工作版本添加了一个人为的参数以使其工作,我宁愿避免。
我试图理解为什么在损坏的版本中,当调用堆栈作为 currNode 在 LIM 开始展开并返回到 MEX 作为 currentNode 时,为什么它不在 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.
why doesn't the unfinished for loop resume over MEX's neighbors?
因为循环中的 return
语句会立即从循环和函数中中断。
相反,如果递归调用没有找到目的地,您需要查看 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;
}
如果您尝试构建一个 return 路径的 DFS,而不仅仅是目的地是否可达的布尔值,模式应该会变得更加明显。
顺便说一句,我建议对 visited
使用 Set
,与其在循环内检查 neighborData === destination
,不如在循环外进行基本情况比较 curreNode.data === destination
循环(实际上,这是一个错误,您的版本不适用于搜索从节点到自身的路径)。