Skip to main content

Section 31.9 Removing Values

Subsection 31.9.1 Overview of Removal

Removal of a value from a BST is a bit more complicated than insertion or finding a value. Like insertion, we start by recursively searching for the node with that value. As we recurse, we will update the pointer on the side of the recursion that leads to the node we are removing (lines 7, 10 below). Each recursive call is responsible for returning the node that will be in that position after removal.
Node* removeRecursive(Node* node, int value) {
    if (node == nullptr) {
        return nullptr;  // value not found, nothing is there
    }
    if (value < node->data) {
        // Value is in left subtree
        node->left = removeRecursive(node->left, value);
    } else if (value > node->data) {
        // Value is in right subtree
        node->right = removeRecursive(node->right, value);
    } else {
        // Node with the value found
  }
    return node;  // node remains unchanged
}
That logic is missing the key part at line 12: what to do once we find the node to be removed. That logic not only needs to delete the node, it needs to figure out what to do with any children that are hanging off that node. There are a few cases to consider:
  • The node to be removed is a leaf (has no children).
  • The node to be removed has one child.
  • The node to be removed has two children.
The rest of the logic we will consider in this section will go in the else block at line 12.

Subsection 31.9.2 No Children (Leaf Node)

The easiest case to handle is when the node to be removed is a leaf node, meaning it has no children. In this case, we can simply delete the node and return nullptr to indicate that this position in the tree is now empty.
Listing 31.9.1.
...
if (node->left == nullptr && node->right == nullptr) {
    // Case 1: No children
    delete node;
    return nullptr;  // Position is now empty
}
...
The removes the leaf node and returns a null pointer to the caller, which will update its child pointer accordingly.

Subsection 31.9.3 One Child

If the node to be removed has only one child, we can remove the node and connect its parent directly to its child. This effectively bypasses the node being removed. Whether the child is on the left or right, that node will be a valid replacement for the removed node.

Activity 31.9.1. One Child Removal.

The animation is set to remove 15. Use the > button to step through the removal process.
Once it is done, try removing 54 (click on the 54 node then press Remove).
Instructions.
When the > button is highlighted, an animation is prepared to run. Use the Step button to step through the animation one step at a time.
When no animation is in progress, you can use the data controls to start a new animation.
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.
  • Core Controls.
    • << : Skip to the beginning of the animation.
    • < : Step backwards one step in the animation.
    • > : Step forward one step in the animation.
    • >> : Skip to the end of the animation.
    • Auto Step Speed Set to anything other than off to automatically step through the animation at the selected speed.
    • Zoom Set the zoom level. You can also use Ctrl + Mouse Wheel to zoom in and out.
  • Data Controls.
    • Value Use this area to enter a value when inserting, finding, deleting, etc...
Below is the code to handle the one-child case. We need to use a temporary pointer to hold onto the child node before we delete the node being removed. Then we can return the child pointer to the caller, which will update its child pointer to bypass the removed node.
Listing 31.9.2.
...
else if (node->left == nullptr || node->right == nullptr) {
    // Case 2: One child
    Node* child;
    if (node->left != nullptr) {
        child = node->left;  // Has left child
    } else {
        child = node->right; // Has right child
    }
    delete node;
    return child;  // child replaces the node
}
...

Subsection 31.9.4 Two Children

The most complex case is when the node to be removed has two children. In this case, we can’t just have the parent adopt the children of the removed node, as it only has one pointer to connect to a child.
Instead, in this case, we will find a replacement value to put in the node of the value being removed. The node itself will remain in place, we will just swap out its value. Then, we will go remove the node that originally held the replacement value (which will now be a duplicate).
That replacement value will be the next largest value in the tree, which is the smallest value in the right subtree of the node being removed. It is guaranteed that this value will have at most one child (it can’t have a left child, or it wouldn’t be the smallest value). So when we remove that node, we will be in either the no-children or one-child case.

Activity 31.9.2. Two Children Removal.

The animation is set to remove 54. Use the > button to step through the removal process.
Once it is done, try removing other nodes with two children (click on the node then press Remove ).
Instructions.
When the > button is highlighted, an animation is prepared to run. Use the Step button to step through the animation one step at a time.
When no animation is in progress, you can use the data controls to start a new animation.
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.
  • Core Controls.
    • << : Skip to the beginning of the animation.
    • < : Step backwards one step in the animation.
    • > : Step forward one step in the animation.
    • >> : Skip to the end of the animation.
    • Auto Step Speed Set to anything other than off to automatically step through the animation at the selected speed.
    • Zoom Set the zoom level. You can also use Ctrl + Mouse Wheel to zoom in and out.
  • Data Controls.
    • Value Use this area to enter a value when inserting, finding, deleting, etc...
Here is the code to handle the two-children case. We first find the minimum value in the right subtree, replace the value in the current node with that minimum value, and then recursively call removeRecursive on the right child to remove the duplicate value.
Listing 31.9.3.
...
else {
    // Case 3: Two children
    // Find the minimum value in the right subtree
    int minValue = findMin(node->right);
    node->data = minValue;  // Replace value with min value
    // Remove the duplicate value from the right subtree
    node->right = removeRecursive(node->right, minValue);
}
...
Note that in this case, we do not have a delete. We are just changing the current node. We rely on the recursive call to find and remove a node and thus end up with one fewer node in the tree.
Even when we continue the removal process note that we end up following a single path down the tree, just like we do when inserting or finding a value. Thus it should take \(O(\log n)\) time.
If we end up in the two child case, we may make two recursive trips down the tree: one to find the value to replace the current node’s value with and the other to remove the duplicate minimum value. However, two trips that are each \(O(\log n)\) add up to just \(O(\log n)\text{.}\) We could develop a more complex algorithm that finds and removes the minimum value in a single pass, but it would not improve the overall time complexity.

Checkpoint 31.9.1.

Checkpoint 31.9.2.

We have found the node we want to remove and it has no children. What do we need to do? You will not use all blocks.

Checkpoint 31.9.3.

We have found the node we want to remove and it just has a left child. What do we need to do? You will not use all blocks

Checkpoint 31.9.4.

We have found the node we want to remove and it has two children. What do we need to do? You will not use all blocks
You have attempted of activities on this page.