We’ve been looking at non-recursive algorithms for deleting the nodes of a binary tree. The previous algorithm performed a postorder traversal, cleverly repurposing the left link as a parent link once the left child pointer is no longer needed.
As I noted at the start of the discussion, the algorithm I came up with was quite different.
For ease of diagramming, let’s draw the binary tree rotated counter-clockwise 45 degrees, so that the root node in the northwest corner, with left nodes going down the page and right nodes going to the right. I focused on the two pieces coming from the root node: The long left (down) chain and as the right child.
| A | → | 🌳 |
| ↓ | ||
| B | → | … |
| ↓ | ||
| C | → | … |
| ↓ | ||
| â‹® | ||
| ↓ | ||
| Z | → | … |
Before I can delete the root node, I have to arrange for the left and subtrees to be cleaned up eventually. My strategy is first to take the right subtree and graft it onto the end of the leftmost chain:
| A | ||
| ↓ | ||
| B | → | … |
| ↓ | ||
| C | → | … |
| ↓ | ||
| â‹® | ||
| ↓ | ||
| Z | → | … |
| ↓ | ||
| 🌳 |
At this point, the root node has only one child, namely the left child. So I can delete the root node and treat the left child as the new root of what remains.
| A | 🔥 | |
| B | → | … |
| ↓ | ||
| C | → | … |
| ↓ | ||
| â‹® | ||
| ↓ | ||
| Z | → | … |
| ↓ | ||
| 🌳 | ||
I have now reduced the problem to a smaller problem: The new tree rooted at B has one fewer node than the one I started with, so I can repeat the process until all the nodes are deleted.
void DeleteTree(Node* node)
{
// Empty tree: Nothing to do
if (!node) return;
// Find the leftmost node.
Node* last = node;
while (last->left) {
last = last->left;
}
while (node)
{
// Make the right subtree the left subtree
// of the leftmost node.
last->left = node->right;
// Find the new leftmost node.
while (last->left) {
last = last->left;
}
// We have arranged for the eventual
// deletion of all the children. Now we can
// delete the root node and promote the left
// child to be the new root.
delete std::exchange(node, node->left);
}
}
I like this algorithm because it is shorter and much easier to get correct. The order of element destruction is preorder-ish: Every parent is destroyed before its children, and every left child is destroyed before its right sibling. However, the right subtree is not destroyed immediately after the left subtree, so it’s not a true preorder. You can think of it as a topological order.
The running time of this algorithm is O(n) because each node is deleted once, each right link is traversed once (to graft it to the bottom), and each left link is traversed twice (once to find the leftmost node and once when it becomes the root element and is detached from the rest of the tree prior to being deleted).
Bonus chatter: Note that the right thing happens if the root has no right child: A null tree gets grafted to the end of the leftmost node (which has no effect).
0 comments
Be the first to start the discussion.