Because our IntBST manages dynamic memory, it needs to implement a destructor, copy constructor, and assignment operator to ensure that memory is properly managed when a tree goes out of scope or is copied.
The binary search tree is responsible for the all of the memory used by its nodes. Therefore, when the tree is destroyed, it needs to free all of that memory. This is most easily accomplished with a recursive helper function that deletes all nodes in a subtree rooted at a given node. The destructor can then call this helper on the root of the tree.
We need to visit every node in the tree, which means one of our standard tree traversals will be required. As to which traversal to use, a post-order traversal is the most natural fit. When we reach a node, we will first delete its left and right subtrees so that we donβt lose track of them. Then, we can delete the current node itself.
void clearRecursive(Node* node) {
if (node == nullptr) {
return; // Base case: nothing to delete
}
// Recurse on left and right subtrees
clearRecursive(node->left);
clearRecursive(node->right);
// Now delete this node
delete node;
}
Then we need a member function that calls this helper. We will use it internally for things like the destructor, but it also is a reasonable operation to make available to consumers of the IntBST class. So we will make it a public member function.
When clear is used from the destructor, the entire tree is going away. So it would be OK to not bother resetting the root. However, when clear() is called in other situations, we may intend to continue using the BST. So clear should make sure the root is set to nullptr so we arenβt pointing at deleted memory.
Subsection31.7.2Copy Constructor and Assignment Operator
To implement a copy constructor and assignment operator for the binary search tree, we need a way to copy all of the nodes from one tree to another. We want to do a deep copy, meaning that we create an entirely new set of nodes with the same values and structure as the source tree.
Our parameter will be the node in the source tree that we want to copy. Our job will be to make a new node with the same value, recursively copy its left and right subtrees, and then return the address of the new node we created. This time, a pre-order traversal makes the most sense. We need to create the current node first, and then we can recursively create its children and attach them.
Node* copyRecursive(Node* sourceNode) {
if (sourceNode == nullptr) {
return nullptr; // Base case: nothing to copy
}
// Create new node with same value
Node* newNode = new Node(sourceNode->data);
// Recursively copy left and right subtrees
newNode->left = copyRecursive(sourceNode->left);
newNode->right = copyRecursive(sourceNode->right);
return newNode; // Return address of new node
}
Note how we make each recursive call, which will return a pointer, and then store that as the address of the new nodeβs appropriate child.
You can click and drag on the animation area to pan around. Use the Ctrl + mouse wheel (or touchpad scroll gesture) while over the animation area to zoom in and out.
Now we can implement the copy constructor and assignment operator using this helper function. The copy constructor will call the helper on the source treeβs root and set its own root to the result. The assignment operator will first clear its own tree to avoid memory leaks, then call the helper on the source treeβs root, and finally set its own root to the result. (It will also need to do the usual self-assignment check and return *this.)
\(O( \log n)\) is only when we walk one path from root to a leaf.
\(O(1)\)
That would only be true if we were just resetting the root and not actually deleting any nodes. However, that would cause a memory leak since the nodes would still be in memory but we would have no way to access them to delete them later.