【问题标题】:How to design C++ tree and node classes? [closed]如何设计 C++ 树和节点类? [关闭]
【发布时间】:2012-10-07 05:01:04
【问题描述】:

我所做的如下,但是我在破坏树和尝试打印树时遇到了很多问题(基本上我需要在树上使用递归的任何地方)。

这是因为在尝试在右子树的左侧递归调用 print 时,我的方法中断了,因为我的左右子树实际上只有 Nodes 而不是 Trees。因此,我需要将我的节点类型化为 Trees,或者我需要创建新的树,这两者都是丑陋的解决方案。

我认为这里的问题在于类设计。你能评论一下吗?谢谢!

class Node {
    int _data;
public:
    Node* left;       // left child
    Node* right;      // right child
    Node* p;          // parent
    Node(int data) {
        _data = data;
        left = NULL;
        right = NULL;
        p  = NULL;
    }
    ~Node() {
    }
    int d() {
        return _data;
    }
    void print() {
        std::cout << _data << std::endl;
    }
};

class Tree {
    Node* root;
public:
    Tree() {
        root = NULL;
    }
    Tree(Node* node) {
        root = node;
    }
    ~Tree() {
        delete root->left; // this is NOT RIGHT as
                           // it only deletes the node
                           // and not the whole left subtree
        delete root->right;
        delete root;
    }

    void print(int);
    void add(int);
};

【问题讨论】:

  • Node *p 应该是什么?一个节点只有左右孩子...
  • 什么是 Node::p?编辑:该死的 nneonneo:P
  • p 是父指针。
  • 啊。说得通。 (可能应该称为*parent)。
  • 查看this implementation,我觉得设计不错。那里缺少析构函数,您只需要按照 Rollie 在他的回答中指出的方式进行即可

标签: c++ data-structures tree


【解决方案1】:

你为什么不让Node 成为你的树类?根据定义,非空节点是某棵树的根。这将大大简化您的代码,因为您不需要为 TreeNode 制作不同的案例。

【讨论】:

  • 因为我想创建一个通用的 Node 结构,它不仅可以用于树,还可以用于其他数据结构,例如链表,并且树由节点集。
  • 这确实简化了算法,但有些概念不同。例如,您希望 Tree::Print() 递归地打印所有节点,但不是 Node::Print(),或者如果存在包装所有内容的 Tree 类,则无需在每个节点中跟踪节点计数。
  • 但是您的 Node 结构包含 leftright 指针。它们真的不能被解释为通用的!如果你愿意,请给他们打电话TreeNode。实际上,TreeNodeListNode 应该是不同的类。
  • @Rollie: Node 可能会受益于递归打印功能。它还可以具有非递归打印功能。两者不必是排他的。还要注意,让Nodes 充当它们自己树的根,这样您就可以轻松地单独检查子树,而无需将它们的内容复制到新的Tree
  • @nneonneo 当然是这样——这只是编写相同函数的两种方式。无论它是 Node 的一部分并显式地 PrintRecursive()、Tree 的一部分作为 Print(),还是只是一个算法 Print(Node *, bool bRecursive = false)。我的陈述仅仅是拥有树类有一些价值。并不是说它在所有方面都优越。虽然 OP 描述他的 Node 类的方式,但如果在 List 类中使用 PrintRecursive() ,则不会以相同的方式实现。再说一遍 - 实现可以转换为列表的树的最佳/优雅方式是什么?
【解决方案2】:

更改 Node 的析构函数,使其删除其左右子节点。然后为你的树析构函数删除根,所以:

~Tree() {
   delete root;
}

~Node() {
   delete left;
   delete right;
}

【讨论】:

  • 其他选择是使用智能指针进行内存处理,这样您就可以忘记析构函数,您只需在所有声明中使用 std::unique_ptr 而不是 Node*。例外是 p (父),它应该仍然是 Node* 因为你没有在那里处理内存,只是指向。
  • 我还没有使用 unique_ptr,但我正在查看的文档表明它不可复制 - 这可能会使重新平衡更加困难。也许 shared_ptr 会更合适?
  • shared_ptr 引入了意外循环引用的一定风险。你仍然可以移动 unique_ptr,这没什么大不了的。只需使用 target = std::move( source ); 而不是 target = source;targetsource 将是 std::unique_ptr&lt;Node&gt; 类型)。
  • 如果我理解的话,这似乎是合理的:考虑你有一个 3 节点不平衡树的情况(值 3、2、1,3 是根)。假设 Tree 使用 unique_ptr&lt;Node&gt; root;,为了平衡,您首先必须使用 Node2-&gt;right = move(root);,然后是 root = move(Node3-&gt;left); - 顺序似乎突然变得很重要,因为执行相反的顺序会删除 Node3。这不是一个无法克服的问题,但仍然令人担忧。
【解决方案3】:
public:
~Tree() {
        clear();
        delete root;
    }

    void clear()
    {
        recursive_delete(root);
    }
private:
    void recursive_delete(Node* node)
    {
        if (node!=NULL)
        {
            recursive_delete(node->left);
            recursive_delete(node->right);
            deleteNode(node);
        }
    }
    void deleteNode(Node* node)
    {
        delete node;
        node = NULL;
    }

【讨论】:

  • 您正在删除~Treerecursive_delete 中的root。
猜你喜欢
  • 2016-02-16
  • 2013-06-30
  • 2017-05-02
  • 2017-04-30
  • 1970-01-01
  • 1970-01-01
  • 2013-07-06
  • 2017-05-29
  • 1970-01-01
相关资源
最近更新 更多