C++ 是否需要遍历普通树来销毁该树(析构函数)?

C++ 是否需要遍历普通树来销毁该树(析构函数)?,c++,tree,destructor,C++,Tree,Destructor,当销毁(当对象超出范围时)常规树时,是否有必要像处理双链表一样遍历每个节点并删除它们?我正在写的通用树是一个循环树,因此可以在固定的时间内进行插入。如果有人能帮我纠正这个错误,那将非常有帮助。下面的析构函数当前导致堆栈溢出(我认为这是因为树是循环的) 这是两个析构函数 ~Node() { if(left){delete left;} if(next){delete next;} if(parent)

当销毁(当对象超出范围时)常规树时,是否有必要像处理双链表一样遍历每个节点并删除它们?我正在写的通用树是一个循环树,因此可以在固定的时间内进行插入。如果有人能帮我纠正这个错误,那将非常有帮助。下面的析构函数当前导致堆栈溢出(我认为这是因为树是循环的)

这是两个析构函数

~Node()
         {
        if(left){delete left;}
                if(next){delete next;}
                if(parent){delete parent;}
          }
一般树

 ~Gen()
       {
           if (head)
                 delete head; //call destructor on node
                 head = nullptr;
                 m_size = 0;
        }
这是节点类

class Node {
public:
    typedef Node* nodePtr;
    int data;
    //Left child- right sibling implementation
    nodePtr left, next, parent;
    int rank; //will be used for merging.
~Node()
         {
               if(left){delete left;}
                if(next){delete next;}
                if(parent){delete parent;}
          }
        
private:
    
    Node & operator =(const Node&);
};
这是使用上面节点的树

class Gen{
 public:
      typedef Node* nodePtr;
       Gen():m_size(0),head(0){}
       ~Gen()
       {
           if (head)
                 delete head; //call destructor on node
                 head = nullptr;
                 m_size = 0;
        }
        void push(int val)
        {
        nodePtr newNode = new Node;
        
        newNode->data = val;
        newNode->rank = 0;
        newNode->left = newNode->next = newNode->parent = 0; //set all pointers to null
        insertRoot(newNode); //call the inserthelper
        ++m_size;
        }
        //other functions (deleteMin, decreaseKey etc)
private:
int m_size;
nodePtr head;
nodePtr insertRoot(nodePtr newNode)
{
   //create a circular link
   if (!head)
    {
        head = newNode;
        newNode->next = newNode;
    }
    else
    {
        newNode->next = head->next;
        head->next = newNode;
        if (newNode->data < head->data)  //min heap (lazy insert)
            head = newNode;
    }
}
};

class-Gen{
公众:
typedef节点*nodePtr;
Gen():m_大小(0),头(0){}
~Gen()
{
若有(总目)
delete head;//在节点上调用析构函数
水头=零PTR;
m_大小=0;
}
无效推送(int val)
{
nodePtr newNode=新节点;
newNode->data=val;
newNode->rank=0;
newNode->left=newNode->next=newNode->parent=0;//将所有指针设置为null
insertRoot(newNode);//调用inserthelper
++m_尺寸;
}
//其他功能(deleteMin、decreaseKey等)
私人:
国际货币单位大小;
无受体头;
nodePtr insertRoot(nodePtr newNode)
{
//创建一个循环链接
如果(!头)
{
头=新节点;
newNode->next=newNode;
}
其他的
{
新建节点->下一步=头部->下一步;
head->next=newNode;
if(newNode->datadata)//最小堆(惰性插入)
头=新节点;
}
}
};

您需要以某种方式调用树的每个成员的析构函数,可以通过递归方式(如您的实现)遍历树,也可以通过迭代方法

最简单的迭代方法是使用一个堆栈,在树的下方添加元素,在树的上方弹出元素以删除它们。请在中查看有关此方法的更多信息

因此,在您的特定情况下,通用树将有一个干扰物穿过树来破坏节点,而您不需要节点析构函数。析构函数将类似于:

#include <stack>
...
....
~Gen() {
    std::stack<Node*> s;
    s.push(head);
    Node* current;
    while (!s.empty()) {
        current = s.top();
        s.pop();
        if (current->left != nullptr) {
            s.push(current->left);
        };
        if (current->next != nullptr) {
            s.push(current->next);
        }
        delete current;
    }
    head = nullptr;
}
#包括
...
....
~Gen(){
std::堆栈s;
s、 推(头);
节点*电流;
而(!s.empty()){
电流=s.top();
s、 pop();
如果(当前->左!=nullptr){
s、 推送(当前->左);
};
如果(当前->下一步!=nullptr){
s、 推送(当前->下一步);
}
删除当前文件;
}
水头=零PTR;
}
因此,当您沿着树向下移动时,将子对象添加到堆栈中,然后删除父对象。堆栈为空后,将删除树中的所有节点

是否需要遍历普通树来销毁该树(析构函数)

是的,必须访问每个节点才能删除它们(或者至少访问每个分支,因为您可以从分支中删除叶子)


我假设节点X的
父节点
指向这样一个节点,其子节点X是。实现节点析构函数的一个问题是,父节点的析构函数删除其子节点,而其子节点删除父节点,该父节点删除子节点,该子节点删除父节点,该父节点删除子节点,该子节点删除子节点,该子节点删除子节点。。。你能发现问题吗?递归是无止境的。此外,父对象的生存期已经结束,因此试图删除父对象的子对象将导致未定义的行为

解决方法很简单:不要删除父项。如果从根开始删除子节点,并且如果父节点总是指向已被删除的节点,则可以根据需要访问树的所有节点


另一个问题是树不平衡,所以在最坏的情况下,树的深度可能与元素的数量成线性关系。这会导致析构函数的递归深度线性增长(在最坏的情况下),这可能导致堆栈溢出,即使无限循环不是问题

您不应该使用递归来破坏不平衡的树。应该对节点使用普通析构函数,并对树实现迭代析构函数

破坏不平衡二叉树的一个好算法是旋转一个子树,直到它为空,然后删除根并重复另一个子节点。示例(完全未经测试,可能有问题):


我正在写的树是一个圆形的

这也破坏了析构函数的实现,因为当到达指向根的“叶”时,将进入类似的无限循环和上述未定义的行为


不幸的是,这个属性也破坏了我建议的迭代析构函数。不过我现在没有时间想出一个好的解决办法。一个想法是将其与循环检测算法相结合。

如果(父项){delete parent;}
您可能不希望这样;它导致双重破坏。父级拥有它的子级,并负责删除它们,而不是相反。在构建时,没有O(1)机制来删除整个树。它需要像组装一样被抹掉;是的,
node->next
链接形成一个循环列表这一事实也会导致双重破坏,一旦你回到已经被破坏的节点。您可能希望迭代而不是递归地实现销毁。OP没有很好地描述他们的数据结构,但他们似乎在实现左-子-右兄弟树,在这种情况下,
left,next
是一致的和常规的命名。在这种情况下,名称
General tree
具有误导性,我删除了注释,因为这不是问题的本质,也不是答案。我添加了一个标志来指示节点是否被访问,并通过post order traversa将其删除
while (head) {
    if (head->left) {
        // rotate
        Node* temp_left = head->left;
        head->left = head->left->next;
        temp_left->next = head;
        head = temp_left;
    } else {
        Node* temp_next = head->next;
        delete head;
        head = temp_next;
    }
}