Deleting BST Nodes

We have the following tree:

Now suppose that we're asked to delete a node associated with a particular value in the tree. Call it the target node. How might we do so? Once again, because we know how to search for nodes, deleting is almost trivial, at least for tree's leaves. We just to search for the particular node containing the key, then delete if found. For the leaves, this is straightforward. If we wanted to delete the node containing 70, we just have call search(root, 70), then, when we get to 70, delete:

All that's left to do after deleting node(70) is to ensure its parent, node(60) has its left-child pointer set to NULL. Now, we emphasized almost trivial because there's a bit of a troublesome case: Deleting branch nodes. For example, suppose we were asked to delete the node containing 60. If we just blindly deleted the node, we'd end up with:

We've partitioned the tree, and that's likely not what we intended. But even if we did, assuming the only way to access the tree was through a single root pointer, the other tree — node(55) — is lost in memory; from a practical perspective, we've essentially destroyed it.

To avoid this problem: Before deletion, always check if the target node is a branch node or a leaf node. If the target node is a branch node with only one child, as is the case with node(60), then the target node must take its parent's place after deletion:

This avoids partitioning, but only for deleting uniparous nodes. What if we were now asked to delete node(50)? Once again, blindly deleting causes a partition:

There are several ways to avoid this problem. One way is to fill the newly-emptied position with the target node's inorder predecesor. In other words, fill the vacancy with the node that comes before the target node within the tree's inorder traversal sequence. For our tree above, the inorder traversal sequence is:

10,15,20,30,40,50,55 \lang 10, 15, 20, 30, {\color{forestgreen}{40}}, {\color{indianred}{50}}, {\color{dodgerblue}55} \rang

and we can see that node(40) comes before node(50). Above, we've also indicated the blue-colored node(55), node(50)'s inorder successor. Thus, if we delete a biparous node, we can choose either the inorder predecesor or the inorder successor to fill the vacancy:

Inorder predecessor sucession:

Inorder successor succession:

Given the binary search tree's structure, finding either of these successors is easy enough, per the following lemmas:

Definition: Inorder Predecesor. Let N{N} be a node in a binary search tree B.{B.} The inorder predecesor of N{N} is the rightmost child of B{B}'s left subtree.

Definition: Inorder Successor. Let N{N} be a node in a binary search tree B.{B.} The inorder successor of N{N} is the leftmost child of B{B}'s right subtree.

We call this vacancy-filling technique inorder-succession. Having given a broad overview of how the technique works, let's point out some of the flaws with this technique. First, suppose we had the following tree:

The inorder traversal squence for this tree is:

10,20,30,70,80,90 \lang 10, 20, 30, 70, 80, 90 \rang

If we deleted node(20) using inorder-successor-succession, then we suddenly have to perform several successions:

Before deletion. No changes.

10,20,30,70,80,90 \lang 10, 20, 30, 70, 80, 90 \rang

Remove node(20). There's a vacancy that must be filled, and because we're using inorder-successor-succession, node(30) must rise to fill the vacancy. In this case, it becomes the left-child of node(90).

10,30,,70,80,90 \lang 10, 30, \square, 70, 80, 90 \rang

The previous step results in a vacancy, and now node(70) must succeed.

10,30,70,,80,90 \lang 10, 30, 70, \square, 80, 90 \rang

Now node(80) must succeed.

10,30,70,80,,90 \lang 10, 30, 70, 80, \square, 90 \rang

And finally, node(90) must succeed, which means node(30) must succeed.

10,30,70,80,90 \lang 10, 30, 70, 80, 90 \rang

Time Complexity

Examining this process, we see that the time complexity for removing nodes depends on the tree's height. We must search — i.e., traverse — the tree and modify accordingly.