递归克隆图时的堆栈溢出

stackoverflow when cloning graph recursively

本文关键字:堆栈 栈溢出 递归      更新时间:2023-10-16

我收到错误"地址消毒器..运算符 new 中的堆栈溢出(无符号长(">使用此版本的代码,其中我使用复制>neighbors.push_back

class Node {
public:
int val;
vector<Node*> neighbors;
Node() {}
Node(int _val, vector<Node*> _neighbors) {
val = _val;
neighbors = _neighbors;
}
};
unordered_map<Node*, Node*> copies;
Node* cloneGraph(Node* node) {
if(!node) return node;
if(copies.find(node)==copies.end()){
Node *copy = new Node(node->val,{});
for(auto neighbor:node->neighbors){
copy->neighbors.push_back(cloneGraph(neighbor));//stackoverflow 
}
copies[node]= copy;
}
return copies[node];
}

但它适用于我使用副本[节点]->neighbors.push_back的这个版本,为什么会这样?
唯一的区别是使用对全局映射元素的引用:副本[节点]与本地指针副本

Node* cloneGraph(Node* node) {
if(!node) return node;
if(copies.find(node)==copies.end()){
copies[node] = new Node(node->val,{});
for(auto neighbor:node->neighbors){
copies[node]->neighbors.push_back(cloneGraph(neighbor));
}
}
return copies[node];
}

在第一个实现中,您将为每个推送到堆栈的递归调用创建一个新的 Node。然而,在您的第二个实现中,它被放置在一个不属于局部递归变量的数组中(它看起来像一个全局变量(,因此堆栈不需要跟踪新创建的节点。

当递归函数导致堆栈溢出时,您应该寻找的第一件事就是无限递归。

考虑一个有两个节点的简单图:AB的邻居,BA的邻居(对于无向图来说非常标准(。当你打电话给cloneGraph(&A)时会发生什么?

  1. 节点A不在映射中,因此会进行克隆。
  2. 作为克隆过程的一部分,cloneGraph(&B)被称为。

那么接下来会发生什么?

  1. 节点B不在映射中,因此会创建一个克隆。
  2. 作为克隆过程的一部分,调用cloneGraph(&A)

好了,回到我们开始的地方。如果递归继续,这可能会变得丑陋。所以最大的问题是

  1. 此时,地图中是否A

使用代码的第一个版本,它不是。因此,递归重复,直到堆栈溢出。使用代码的第二个版本,它是,所以递归在这一点上结束。

在第一个版本中,你有一个递归,它可以为带有循环的图生成无限循环。请注意,进入更深层次递归的条件是在映射copies中找不到节点,但此映射仅在整个递归完成后更新。

如果你的图是 A->B 和 B->A,那么对cloneGraph(&A)的调用将调用cloneGraph(&B),这将无限期地调用cloneGraph(&A)等等,直到调用堆栈没有更多的空间。

仔细考虑你的算法。大概你的图有cylces。

由于您的第一个版本仅在递归到cloneGraph后将新创建的节点添加到copies,因此下一次调用将尝试再次克隆同一节点,这将递归等。