Skip to content

Binary search trees

A binary search tree is a binary tree sorted such that for each node, every node’s key in its left subtree is less than or equal to its key’s value, and every node’s key in its right subtree is greater than or equal to its key’s value.

Operation Average Worst
Access $\htmlClass{text-green}{\Theta(\log(n))}$ $\htmlClass{text-yellow}{\mathcal{O}(n)}$
Insert $\htmlClass{text-green}{\Theta(\log(n))}$ $\htmlClass{text-yellow}{\mathcal{O}(n)}$
Delete $\htmlClass{text-green}{\Theta(\log(n))}$ $\htmlClass{text-yellow}{\mathcal{O}(n)}$

The sorting properties of a binary search tree allow for efficient searching, since for each comparison up to half of the remaining subtree can be eliminated.

A tree demonstrating the sorting properties of a binary search tree

(original)

Maximum and minimum

To find the maximum key in a binary search tree (or subtree), starting with the root node:

Finding the maximum key in a binary search tree through right traversal

(original)

To find the minimum key in a binary search tree (or subtree), starting with the root node:

Finding the minimum key in a binary search tree through left traversal

(original)

Successor and predecessor

A node’s successor is the node with the smallest key greater than that node’s key. To find a node’s successor:

A node’s predecessor is the node with the largest key less than that node’s key. To find a node’s predecessor:

In-order traversal

For each node:

  1. Recursively traverse the left subtree
  2. “Visit” the node
  3. Recursively traverse the right subtree

The implementation below has an example of this recursive function.

Operations

Access

Beginning with the root node, compare the node’s key value to the search key:

Finding the binary search tree node with key 4

(original)

Insertion

Beginning with the root node, compare the node’s key value to the insertion key:

Inserting a binary search tree node with key 5

(original)

Deletion

First, access the node to be deleted. If it is found, there are 3 scenarios:

  1. Leaf node (no left or right child):
    • If the node has a parent, remove its reference to the node
    • If the node is the root, the tree is now empty

Deletion of a leaf node from a binary search tree

(original)

  1. Internal node (1 child):
    • If the node has a parent, its reference to the node is replaced with the child node
    • If the node is the root, the child becomes the new root

Deletion of an internal node with 1 child from a binary search tree

(original)

  1. Internal node (2 children):
    • Find the node’s successor (since the node always has a right subtree in this scenario, the successor will always be contained in that subtree)
    • Replace the node’s key/value with that of the the successor
    • Delete the successor from the right subtree, which reduces to repeating scenario 1 or 2 above for the successor (since the successor is the left-most node in the subtree, it will always have either 0 children or 1 right child):
      1. If the successor is a leaf node, remove its parent’s reference to the successor node
      2. If the successor has a right child, the parent’s reference to the successor node is replaced with the successor’s right child

Deletion of a node with two children, where either the node’s successor is a leaf, or has a right child

(original)

Implementation

This generic example implementation stores key-value pairs and is adaptable as a map ADT :

  1public class BinarySearchTree<K extends Comparable<K>,V> {
  2    private class Node {
  3        private K key;
  4        private V value;
  5        private Node left, right;
  6
  7        public Node(K key, V value) {
  8            this.key = key;
  9            this.value = value;
 10        }
 11    }
 12
 13    private int size = 0;
 14    private Node root = null;
 15
 16    public int size() { return size; }
 17
 18    public boolean isEmpty() { return size == 0; }
 19
 20    public V search(K key) {
 21        Node result = findNode(key);
 22        return result != null ? result.value : null;
 23    }
 24
 25    private Node findNode(K key) {
 26        Node current = root;
 27        while (current != null && !key.equals(current.key)) {
 28            if (lt(key, current.key))
 29                current = current.left;
 30            else
 31                current = current.right;
 32        }
 33        return current;
 34    }
 35
 36    public V insert(K key, V value) {
 37        Node parent = null;
 38        Node current = root;
 39
 40        while (current != null) {
 41            if (key.equals(current.key)) {
 42                // replace the existing key
 43                V result = current.value;
 44                current.value = value;
 45                return result;
 46            }
 47
 48            parent = current;
 49            if (lt(key, current.key))
 50                current = current.left;
 51            else
 52                current = current.right;
 53        }
 54
 55        Node newNode = new Node(key, value);
 56        if (parent == null)
 57            root = newNode;
 58        else if (lt(key, parent.key))
 59            parent.left = newNode;
 60        else
 61            parent.right = newNode;
 62
 63        size++;
 64        return null;
 65    }
 66
 67    public V remove(K key) {
 68        // find the node to remove, and its parent
 69        Node parent = null;
 70        Node current = root;
 71        while (current != null && !key.equals(current.key)) {
 72            parent = current;
 73            if (lt(key, current.key))
 74                current = current.left;
 75            else
 76                current = current.right;
 77        }
 78
 79        if (current == null) return null; // key not found
 80
 81        V result = current.value;
 82
 83        // leaf node case
 84        if (current.left == null && current.right == null) {
 85            skipNode(parent, current, null);
 86        }
 87        // one child case
 88        else if (current.right == null || current.left == null) {
 89            Node child = current.right == null ? current.left : current.right;
 90            skipNode(parent, current, child);
 91        }
 92        // two children case
 93        else {
 94            // find the node's in-order successor, and its parent
 95            parent = current;
 96            Node successor = current.right;
 97
 98            while (successor != null) {
 99                parent = successor;
100                successor = successor.left;
101            }
102
103            // replace the current node with its successor
104            current.key = successor.key;
105            current.value = successor.value;
106
107            // remove the now-duplicated successor
108            skipNode(parent, successor, successor.right);
109        }
110        size--;
111        return result;
112    }
113
114    private void skipNode(Node parent, Node deprecated, Node child) {
115        if (parent == null)
116            root = child;
117        else if (parent.left == deprecated)
118            parent.left = child;
119        else
120            parent.right = child;
121    }
122
123    public void forEach(BiConsumer<K, V> consumer) {
124        inOrderWalk(root, (n) -> consumer.accept(n.key, n.value));
125    }
126
127    private void inOrderWalk(Node root, Consumer<Node> consumer) {
128        if (root != null) {
129            inOrderWalk(root.left, consumer);
130            consumer.accept(root);
131            inOrderWalk(root.right, consumer);
132        }
133    }
134
135    private boolean lt(K key, K key2) { return key.compareTo(key2) < 0; }
136}