【问题标题】:Is it necessary to traverse a General Tree for destroying the tree (destructor)?销毁树(析构函数)是否需要遍历General Tree?
【发布时间】:2021-05-30 15:39:04
【问题描述】:

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

这里有两个析构函数

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

【问题讨论】:

  • if(parent){delete parent;} 你可能不想要那个;它导致双重破坏。父母拥有其孩子并负责删除它们,而不是相反。
  • 因为这是构建的,所以没有 O(1) 机制来删除整个树。它需要像组装一样被抹去;一砖一瓦。
  • 是的,node-&gt;next 链接形成一个循环列表这一事实也会导致双重破坏,一旦你绕了一圈回到已经被破坏的节点。您可能希望以迭代方式而不是递归方式实现销毁。

标签: c++ tree destructor


【解决方案1】:

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

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

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

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

因此,当您沿着树前进时,您将子项添加到堆栈中并删除父项。一旦堆栈为空,树中的所有节点都将被删除。

【讨论】:

  • OP 并没有很好地描述他们的数据结构,但他们似乎正在实现左子右兄弟树,在这种情况下 left, next 是一致且传统的命名。
  • 在这种情况下,General Tree 的名称具有误导性,但是,我删除了评论,因为这不是问题的本质,也不是答案。
  • 我添加了一个标志来指示该节点是否被访问并通过后序遍历将其删除。我还是更喜欢你的方法,所以我会改变它。
【解决方案2】:

是否需要遍历General Tree才能销毁树(析构函数)?

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


我假设节点 X 的 parent 指向其子 X 所在的此类节点。您实现节点析构函数的一个问题是父析构函数删除了它的子元素,而它的子元素删除了父元素,删除了子元素,删除了父元素,删除了子元素,删除了父元素,删除了子元素......你能发现问题?递归是无止境的。此外,父母的生命周期已经结束,因此尝试删除父母的孩子会导致未定义的行为。

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


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

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

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

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

我正在写的树是一个圆形

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

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

【讨论】:

    猜你喜欢
    • 2020-12-27
    • 2013-10-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-10-20
    • 2018-06-19
    • 2021-06-05
    • 1970-01-01
    相关资源
    最近更新 更多