Skip to content

Deleting from a binary search tree

A leaf is unlinked; a node with one child is replaced by it; a node with two children takes its successor’s key, and the successor is unlinked instead.

Helps to know first
This topic

First, the picture

Taking a key out of a search tree must leave every smaller key on the left. Watch what happens when the root goes.

Removing a node from a tree

Eight keys; deleting the root, which has two children

void bst_remove(struct tnode **link, int key){    while (*link != NULL && (*link)->key != key) {        if (key < (*link)->key)            link = &(*link)->left;        else            link = &(*link)->right;    }    struct tnode *t = *link;    if (t == NULL)        return;    if (t->left == NULL) {        *link = t->right;        free(t);    } else if (t->right == NULL) {        *link = t->left;        free(t);    } else {        struct tnode **s = &t->right;        while ((*s)->left != NULL)            s = &(*s)->left;        struct tnode *succ = *s;        t->key = succ->key;        *s = succ->right;        free(succ);    }}    bst_remove(&root, KEY);    inorder(root);

All 8 keys are in the tree. Now remove 50.

links re-aimed
0
keys moved
0
Remove
0.00 / 14.05 s
Describe this picture

A picture across the frame, with the code under it. On the left is the tree of Binary search trees with one more key: 50 at the root, 30 and 70 under it, 20 and 40 under 30, 60 and 80 under 70, and 65 as 60’s right child. Each node is a box with its key, its address under it and two dots for its left and right pointers. A ring marks the node the search has reached; once found, the node to remove is marked as the target and tagged t, and during the successor search the ring moves to the node s leads to. On the right is the card for the call bst_remove(&root, 50), with its boxes link, key and t, and later s and succ; link and s are written as the link they hold the address of, such as &root or &70->left. Under both, a row shows the keys in order, read from the tree at every step. The code pane shows bst_remove and main’s line that calls it, with the line about to run marked. The clip removes 50, about 14 seconds: the ring starts on 50, which is found at once; s goes right once, to 70, then left to 60, which has no left child; 60’s key is copied into the root, so for one step the row reads 20 30 40 60 60 65 70 80; then 70’s left pointer swings to 65, the old 60 node leaves the tree and is drawn aside, and it is hatched once freed. The row ends 20 30 40 60 65 70 80. Under the picture are one sentence on the current step, the readouts “links re-aimed” and “keys moved”, which end at 1 and 1, the control and the output. The control, “Remove”, has one button for each key, 20 to 80 in order; a press plays that removal from the full eight-key tree. The transport plays and pauses, steps one line back or forward, and scrubs; a paused step is kept in the page address as remove.step, and the key as remove.key.

A search tree keeps one promise at every node: smaller keys in the left subtree, larger in the right. Removing a key must keep it. In memory, a node is in the tree because one pointer points to it, the root pointer or its parent’s left or right. So removing comes down to one question: which pointer should change, and what should it point to now?

Press the leaves first: 20, 40, 65 or 80. The search walks down to the node, the pointer that led there becomes NULL, and the node is freed. Nothing else moves; the readout says one link re-aimed.

Now press 60. It has one child, 65. The pointer that led to 60 (70’s left) is aimed at 65 instead, and 60 is freed. 65 moves up into 60’s place, and the order still holds: everything under 60 was already between 50 and 70, as 60 was.

Now 50, the root, which the clip plays. Its two children can’t both hang from one pointer. The trick is to leave the node where it is and change its key: copy in the successor, the smallest key larger than 50, which is 60. Then remove the old 60 node instead, and that node is easy to remove, because it has no left child. Only one key moves, and one link is re-aimed: 70’s left, from the old 60 to 65.

Key idea

Leaf or one child: the link that points to the node takes its other child, which may be NULL. Two children: copy the successor’s key up, then remove the successor, which has at most one child.

The code and its cost

The program

The full program, bst-remove.c, starts with the two #include lines, struct tnode, new_node and bst_insert copied from bst.c (Binary search trees), and inorder and free_tree from traversals.c (Tree traversals). Its main inserts the eight keys 50 30 70 20 40 60 80 65, removes KEY, prints the tree in order and frees it. The new function is this:

void bst_remove(struct tnode **link, int key)
{
    while (*link != NULL && (*link)->key != key) {
        if (key < (*link)->key)
            link = &(*link)->left;
        else
            link = &(*link)->right;
    }
    struct tnode *t = *link;
    if (t == NULL)
        return;
    if (t->left == NULL) {
        *link = t->right;
        free(t);
    } else if (t->right == NULL) {
        *link = t->left;
        free(t);
    } else {
        struct tnode **s = &t->right;
        while ((*s)->left != NULL)
            s = &(*s)->left;
        struct tnode *succ = *s;
        t->key = succ->key;
        *s = succ->right;
        free(succ);
    }
}

link is not a pointer to a node. It holds the address of a pointer: first main’s root, then a node’s left or right field. This is the idea of Removing from a linked list: walk with a pointer to the link, so that when the node is found, *link = … changes exactly the pointer that leads to it. The root needs no special case. If the root itself is removed and has one child, *link is main’s root, and the child becomes the new root.

The call passes &root for that reason: bst_remove may need to change main’s root, so it is given the address of it.

The three cases

t is the node found. If it has no left child, *link = t->right covers two cases at once: a leaf (t->right is NULL, so the link becomes NULL) and a node whose one child is on the right. If it has a left child but no right, the link takes the left child. Either way the child keeps its whole subtree, and every key in that subtree was already on the correct side of every node above.

The successor

With two children, bst_remove finds the successor: one step right, then left until there is no left child. The right subtree holds every key larger than t’s, and its leftmost node holds the smallest of them. s makes the walk as link did, as a pointer to a link, so that afterwards *s is the pointer that leads to the successor.

The successor can’t have a left child. A left child would be smaller than the successor and still in t’s right subtree, so it would be the successor instead. With at most one child, on the right, the successor is removed by the one-child rule: *s = succ->right.

Why the successor? The node’s new key must be larger than everything left of it and smaller than everything right of it. The successor is: it is the smallest key on the right. The predecessor, the largest key on the left (one step left, then right to the end), works the same way. Any other key would break the order somewhere.

Key removedCaseWhat changes
20, 40, 65, 80leafthe parent’s pointer becomes NULL
60one child70’s left points to 65
50two childrenthe root holds 60; 70’s left points to 65
30two children30’s node holds 40; its right becomes NULL
70two children70’s node holds 80; its right becomes NULL

What it costs

The walk to the node and the walk on to its successor go down one path from the root, so a removal visits at most h + 1 nodes, where h is the tree’s height (counted in edges), and does a fixed amount of work at each. Like a search, a removal costs the height: about log₂ n steps in a bushy tree, up to n in a chain. Removing 50 here visits 50, 70 and 60; the tree’s height is 3.

Running it yourself

KEY is a macro, 50 unless the build says otherwise, so one file removes any key:

gcc -std=c11 -Wall -Wextra -pedantic bst-remove.c -o bst-remove
./bst-remove
gcc -std=c11 -Wall -Wextra -pedantic -DKEY=30 bst-remove.c -o bst-remove
./bst-remove

The first prints 20 30 40 60 65 70 80, the second 20 40 50 60 65 70 80. Whatever key is removed, the line is the other seven keys in order, which is the quickest check that the tree is still a search tree.

Worked example

Start from the eight-key tree and remove 50, then 20, then 30.

RemoveCaseKeys in order afterwardsPreorder afterwards
50two children: 60 moves up20 30 40 60 65 70 8060 30 20 40 70 65 80
20leaf: 30’s left becomes NULL30 40 60 65 70 8060 30 40 70 65 80
30one child: 60’s left becomes 4040 60 65 70 8060 40 70 65 80

Each inorder list is the one before it minus the key. The preorder shows the shape: the root is 60 from the first removal on, and after the third, 60 has 40 on its left and 70 (with 65 and 80) on its right.

Two checks. What is the successor of 30 in the eight-key tree? (40: one step right, to 40, which has no left child.) Why can’t the successor have a left child? (A left child would be smaller and still in the right subtree, so it would be the successor.)

Where it’s used

Ordered maps. Every ordered map or set built on a search tree supports removal, and starts with exactly this: find the link, then one of the three cases.

Balanced trees. AVL trees and red–black trees begin their deletions the same way, then repair the balance on the way back up.

Reference card

Code or ideaMeaning
struct tnode **linka pointer to a link: main’s root, or a node’s left or right
link = &(*link)->leftmove down, keeping the address of the pointer that leads on
leaf*link = NULL, then free
one child*link takes the child, then free
two childrencopy the successor’s key, then remove the successor
successorone step right, then left to the end; never has a left child
predecessorone step left, then right to the end
costat most h + 1 nodes, one path from the root
checkinorder prints the remaining keys, still sorted

Platform: C11, gcc on x86-64 Linux: int 4 bytes, pointers 8, little-endian. Addresses in the pictures are short stand-ins, by one rule for the whole course; sizes are the real ones.

Keep reading with a free account.

Trees is a members’ section. An account costs nothing.

By creating an account you accept the terms of use.

Sign in

Already have an account? Use the way you signed up.

Are you 13 or over?

Accounts and email updates are for people aged 13 or over. We keep only your answer, never your date of birth.

End of the lesson

Where to go next.

New lessons by email

Are you 13 or over?

Accounts and email updates are for people aged 13 or over. We keep only your answer, never your date of birth.

Phasorium

Account

Sign inWith Google, GitHub or your email

Subjects

Site

Look