Delete a Key from a BST
28 min · Intermediate
Need help? Ask a tutor on WhatsApp (opens in a new tab)Insert only adds a leaf, but delete has to remove a node from the middle without breaking the BST rule. Which node takes its place depends on how many children it has.
Three cases
Inorder successor of a node is the next larger key in the tree. When the node has a right child, it is the leftmost node of the right subtree.
First search for the key. When the node is found there are three cases. A leaf is simply removed. A node with one child is replaced by that child. A node with two children needs a stand-in key.
The smallest node of a subtree
The successor is the smallest key in the right subtree, so walk left until there is no left child. The same walk finds the minimum of any subtree.
def smallest(node): while node.left is not None: node = node.left return nodeThis loop assumes the node is not None. It costs O(h), the length of the left chain.
Delete returns the new subtree
Delete is recursive and returns the root of the subtree after the removal. The parent stores the result, so removing a leaf or a one-child node is just returning something else.
def delete(node, key): if node is None: return None if key < node.key: node.left = delete(node.left, key) elif key > node.key: node.right = delete(node.right, key) else: return remove_node(node) return nodeGoing down works like search. node.left = delete(node.left, key) links the parent to whatever the child returns. A missing key reaches None and nothing changes.
The found-node cases
If one child is missing, return the other one, which also covers a leaf. With two children, copy the successor's key into the node, then delete that key from the right subtree.
def remove_node(node): if node.left is None: return node.right if node.right is None: return node.left successor = smallest(node.right) node.key = successor.key node.right = delete(node.right, successor.key) return nodeExample 1 · Delete a leaf and a one-child node
class Node: def __init__(self, key): self.key = key self.left = None self.right = None
def insert(node, key): if node is None: return Node(key) if key < node.key: node.left = insert(node.left, key) elif key > node.key: node.right = insert(node.right, key) return node
def smallest(node): while node.left is not None: node = node.left return node
def delete(node, key): if node is None: return None if key < node.key: node.left = delete(node.left, key) elif key > node.key: node.right = delete(node.right, key) else: if node.left is None: return node.right if node.right is None: return node.left successor = smallest(node.right) node.key = successor.key node.right = delete(node.right, successor.key) return node
def preorder(node): if node is not None: yield node.key yield from preorder(node.left) yield from preorder(node.right)
root = Nonefor key in [8, 3, 10, 1, 6, 14, 13]: root = insert(root, key)root = delete(root, 1)print(list(preorder(root)))root = delete(root, 14)print(list(preorder(root)))[8, 3, 6, 10, 14, 13] [8, 3, 6, 10, 13]
What this code does
- Preorder lists a node first, then its left side, then its right side, so it shows the shape.
- 1 is a leaf and just disappears. 14 has one child, 13, which takes its place under 10.
Example 2 · Delete a node with two children
class Node: def __init__(self, key): self.key = key self.left = None self.right = None
def insert(node, key): if node is None: return Node(key) if key < node.key: node.left = insert(node.left, key) elif key > node.key: node.right = insert(node.right, key) return node
def smallest(node): while node.left is not None: node = node.left return node
def delete(node, key): if node is None: return None if key < node.key: node.left = delete(node.left, key) elif key > node.key: node.right = delete(node.right, key) else: if node.left is None: return node.right if node.right is None: return node.left successor = smallest(node.right) node.key = successor.key node.right = delete(node.right, successor.key) return node
def preorder(node): if node is not None: yield node.key yield from preorder(node.left) yield from preorder(node.right)
root = Nonefor key in [8, 3, 10, 1, 6, 14, 4, 7, 13]: root = insert(root, key)root = delete(root, 3)print(list(preorder(root)))[8, 4, 1, 6, 7, 10, 14, 13]
What this code does
- 3 has children 1 and 6. The smallest key in 6's subtree is 4, so 4 takes 3's place.
- Then the old 4 is deleted from the right subtree, a leaf case. The tree matches the picture.
Example 3 · Delete the root
class Node: def __init__(self, key): self.key = key self.left = None self.right = None
def insert(node, key): if node is None: return Node(key) if key < node.key: node.left = insert(node.left, key) elif key > node.key: node.right = insert(node.right, key) return node
def smallest(node): while node.left is not None: node = node.left return node
def delete(node, key): if node is None: return None if key < node.key: node.left = delete(node.left, key) elif key > node.key: node.right = delete(node.right, key) else: if node.left is None: return node.right if node.right is None: return node.left successor = smallest(node.right) node.key = successor.key node.right = delete(node.right, successor.key) return node
def preorder(node): if node is not None: yield node.key yield from preorder(node.left) yield from preorder(node.right)
root = Nonefor key in [8, 3, 10, 1, 6, 14, 4, 7, 13]: root = insert(root, key)root = delete(root, 8)print(root.key)print(list(preorder(root)))10 [10, 3, 1, 6, 4, 7, 14, 13]
What this code does
- The successor of 8 is 10, the smallest key of its right subtree. 10 has no left child, so its right child 14 moves up.
- Delete may change the root, so always write
root = delete(root, key).
Example 4 · A key that is not there
class Node: def __init__(self, key): self.key = key self.left = None self.right = None
def insert(node, key): if node is None: return Node(key) if key < node.key: node.left = insert(node.left, key) elif key > node.key: node.right = insert(node.right, key) return node
def smallest(node): while node.left is not None: node = node.left return node
def delete(node, key): if node is None: return None if key < node.key: node.left = delete(node.left, key) elif key > node.key: node.right = delete(node.right, key) else: if node.left is None: return node.right if node.right is None: return node.left successor = smallest(node.right) node.key = successor.key node.right = delete(node.right, successor.key) return node
def preorder(node): if node is not None: yield node.key yield from preorder(node.left) yield from preorder(node.right)
root = Nonefor key in [8, 3, 10, 1, 6, 14, 4, 7, 13]: root = insert(root, key)before = list(preorder(root))root = delete(root, 99)print(before == list(preorder(root)))single = delete(Node(5), 5)print(single)True None
What this code does
- The search reaches
Noneand nothing changes. - Deleting the only node returns
None, which is an empty tree.
remove_node is the found case of delete. The successor has no left child, so its own delete is the easy case. The rule still holds: the copied key is larger than everything on the left and smaller than the rest of the right. The whole delete costs O(h).
Next step
Check your understanding
11 quick questions