How to design C++ tree and node classes?
Solution 1
Why don't you just allow Node
to be your tree class? A non-empty node, by definition, is the root of some tree. This will substantially simplify your code since you don't need to make different cases for Tree
and Node
.
Solution 2
Change the destructor for Node so that it deletes its left and right children. Then for your Tree destructor just delete root, so:
~Tree() {
delete root;
}
~Node() {
delete left;
delete right;
}
Moeb
Updated on July 09, 2022Comments
-
Moeb almost 2 years
What I have done is as below, but with this I am having a lot of problems while destructing the tree, and while trying to print the tree (basically anywhere I need to use recursion on the tree).
This is because while trying to call
print
recursively on the left of the right subtrees, my method breaks because my left and right subtrees are actually onlyNode
s and notTree
s. So, I need to either typecase my Nodes to Trees or I need to create new trees, both of which are ugly solutions.I think the problem here is with class design. Can you please comment on the same? Thanks!
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); };
-
Moeb over 11 yearsBecause I wanted to create a generic Node structure that can be used not just for a tree, but for other data structures as well, for example, a linked list, and for tree to be composed of a set of nodes.
-
Rollie over 11 yearsThis does simplify the algorithm, but some concepts differ. For example, you would expect
Tree::Print()
to recursively print all nodes, but notNode::Print()
, or keeping track of Node count need not be done in every Node if there is a Tree class wrapping everything. -
nneonneo over 11 yearsBut your
Node
structures contain left and right pointers. They can't really be construed as generic! If you want, call themTreeNode
. Realistically,TreeNode
andListNode
should be different classes. -
nneonneo over 11 years@Rollie:
Node
would probably benefit from having a recursive printing function. It may also have a non-recursive printing function. The two need not be exclusive. Note too that havingNode
s act like the root of their own tree allows you to examine subtrees in isolation easily, without having to copy their contents to a newTree
. -
Rollie over 11 years@nneonneo of course that's true - it's just 2 ways of writing the same function. whether it's part of Node and explicitly PrintRecursive(), part of Tree as Print(), or just an algorithm Print(Node *, bool bRecursive = false). My statement is merely that there is some value in having the tree class; not that it's superior in all respects. The way OP is describing his Node class though, PrintRecursive() would not be implemented the same way if used in a List class. Re that - what would be the most optimal/elegant way to implement a Tree that can transform into a List?
-
nneonneo over 11 yearsTrees and lists are really very different beasts. I don't feel there's any value in trying to unify them. How you implement the linearization of a tree is strongly dependent upon the tree's usage and contents.
-
José Manuel over 11 yearsOther choice is using smart pointers for memory handling, that way you can forget about the destructors, you just have to use std::unique_ptr<Node> instead of Node* in all the declarations. The exception would be the p (parent), it should still be Node* as you are not handling memory there, just pointing.
-
Rollie over 11 yearsI haven't used unique_ptr yet, but the doc I'm looking at suggests it's not copyable - this might make rebalancing more difficult. Maybe shared_ptr would be more appropriate?
-
Rollie over 11 yearsIf you visualize a tree on it's side, with the 'root' being on the left, you can think of a 'binary tree' as a general tree where each node is restricted to have at most 2 children, and a linked list as a general tree where each node is restricted to have at most 1 child. They aren't so completely different. When/why would you unify them? A contrived example: you have a list to start, because you care about insertion order. At some point, you no longer care about insertion, but want it sorted, so you convert it to a tree.
-
José Manuel over 11 yearsshared_ptr introduces a certain risk of accidental cyclic references. You can still move unique_ptr, and its not a big deal. Just do
target = std::move( source );
instead oftarget = source;
(target and source would be of typestd::unique_ptr<Node>
). -
nneonneo over 11 yearsSure. General tree nodes would have a generic
children
list which you could use to implement lists. But, it's definitely overkill just to implement a list, and won't work if you try to do anything past singly-linked lists (e.g. doubly-linked, circularly-linked, etc.) If you're going to convert from one representation to another, you would probably just reinsert everything into a new tree. -
Rollie over 11 yearsSeems plausible, if I'm understanding though: consider the case where you have a 3 node imbalanced tree (values 3, 2, 1, with 3 being root). Assuming Tree uses
unique_ptr<Node> root;
, to balance you would first have toNode2->right = move(root);
, thenroot = move(Node3->left);
- order seems like it suddenly matters a great deal, as doing the opposite order would delete Node3. Not an insurmountable issue, but a concern nonetheless. -
Rollie over 11 yearsIsn't circularly-linked just having a
Node * pParent
? -
OneOfOne about 10 yearsYou are deleting root in
~Tree
and inrecursive_delete
.