递归克隆图形时的stackoverflow

时间:2019-09-03 17:12:26

标签: c++ recursion

使用此版本的代码,我在使用 copy-> neighbors.push_back 时收到错误“ AddressSanitizer ..新操作符中的stackoverflow(无符号长)” em>

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];
}

但是它与我使用 copies [node]-> neighbors.push_back 的该版本一起使用,为什么会发生这种情况?
唯一的区别是使用对全局映射元素的引用: copies [node] 与本地指针 copy

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];
    }

4 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分:2)

当递归函数导致堆栈溢出时,您首先要寻找的就是无限递归。

考虑一个包含两个节点的简单图:AB的邻居,而BA的邻居(非定向图的相当标准) 。致电cloneGraph(&A)会怎样?

  1. 节点A不在地图上,因此进行了克隆。
  2. 在克隆过程中,将调用cloneGraph(&B)

那接下来会发生什么?

  1. 节点B不在地图上,因此进行了克隆。
  2. 在克隆过程中,将调用cloneGraph(&A)

好的,回到我们开始的地方。如果递归继续下去,这可能会很难看。所以最大的问题是

  1. 这时,A在地图上吗?

使用代码的第一个版本,不是。因此重复递归直到堆栈溢出。使用第二版代码,因此递归到此为止。

答案 2 :(得分:1)

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

如果图形是A-> B和B-> A,则对cloneGraph(&A)的调用将调用cloneGraph(&B),这将无限期地调用cloneGraph(&A),依此类推,直到调用堆栈没有空间了。

答案 3 :(得分:1)

请仔细考虑您的算法。大概您的图形具有圆柱。

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