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