Binary Search Trees and Balanced Trees
Intuition
Section titled “Intuition”A filing cabinet that stays sorted: A BST is like a filing cabinet where every folder is labelled, and you know that smaller labels go left and larger labels go right, finding any file takes O(log n) time if the tree is balanced, just like binary search on a sorted array.
Why it matters: BSTs are the foundation of many data structures, balanced BSTs (AVL, red-black) power database indexes, language libraries (C++ std::map, Java TreeMap), and filesystem directories.
The key insight: An unbalanced BST degenerates into a linked list with O(n) operations, this is why self-balancing variants (AVL, red-black) are essential in practice. The balance property guarantees O(log n) height.
Binary Search Tree Fundamentals
Section titled “Binary Search Tree Fundamentals”A binary search tree (BST) is a binary tree where every node satisfies the BST property: for any Node with key All keys in its left subtree are strictly less than And all keys in its Right subtree are strictly greater than .
Node Definition
Section titled “Node Definition”class BSTNode: def __init__(self, key, val=None): self.key = key self.val = val self.left = None self.right = None self.parent = NoneSearch
Section titled “Search”def bst_search(root, key): """ Search for key in BST. Time: O(h) where h = tree height Best case (balanced): O(log n) Worst case (degenerate): O(n) """ curr = root while curr: if key == curr.key: return curr elif key < curr.key: curr = curr.left else: curr = curr.right return NoneInsert
Section titled “Insert”def bst_insert(root, key, val=None): """ Insert key into BST. Returns new root. Time: O(h) """ if root is None: return BSTNode(key, val) if key < root.key: root.left = bst_insert(root.left, key, val) root.left.parent = root elif key > root.key: root.right = bst_insert(root.right, key, val) root.right.parent = root return rootDelete, Three Cases
Section titled “Delete, Three Cases”def bst_delete(root, key): """ Delete key from BST. Returns new root. Time: O(h) """ if root is None: return None
if key < root.key: root.left = bst_delete(root.left, key) elif key > root.key: root.right = bst_delete(root.right, key) else: # Case 1: No children (leaf) if root.left is None and root.right is None: return None # Case 2: One child if root.left is None: return root.right if root.right is None: return root.left # Case 3: Two children, replace with inorder successor successor = bst_min(root.right) root.key = successor.key root.val = successor.val root.right = bst_delete(root.right, successor.key)
return root
def bst_min(node): """Find minimum key node. O(h).""" while node.left: node = node.left return nodegraph TD
subgraph "Delete node 7 (two children)"
A["Before: 5"] --> B[3]
A --> C[7]
B --> D[1]
B --> E[4]
C --> F[6]
C --> G[9]
end
subgraph "After: replace with inorder successor 9"
A2["After: 5"] --> B2[3]
A2 --> C2[9]
B2 --> D2[1]
B2 --> E2[4]
C2 --> F2[6]
endBST Height
Section titled “BST Height”| Shape | Height | Search/Insert/Delete |
|---|---|---|
| Balanced | ||
| Degenerate | ||
| Random | expected | expected |
For distinct keys inserted in random order, the expected height of a BST is approximately . This is only about 39% taller than a perfectly balanced tree, but the worst case (sorted input) gives height .
Traversals
Section titled “Traversals”def inorder(root): """Left -> Root -> Right. Yields sorted order for BST. O(n).""" if root: yield from inorder(root.left) yield root.key yield from inorder(root.right)
def preorder(root): """Root -> Left -> Right. O(n).""" if root: yield root.key yield from preorder(root.left) yield from preorder(root.right)
def postorder(root): """Left -> Right -> Root. O(n).""" if root: yield from postorder(root.left) yield from postorder(root.right) yield root.key
def level_order(root): """Breadth-first traversal. O(n).""" from collections import deque if root is None: return q = deque([root]) while q: node = q.popleft() yield node.key if node.left: q.append(node.left) if node.right: q.append(node.right)Successor and Predecessor
Section titled “Successor and Predecessor”def bst_successor(node): """ Find inorder successor of node. Time: O(h) """ if node.right: return bst_min(node.right) parent = node.parent while parent and node == parent.right: node = parent parent = parent.parent return parent
def bst_predecessor(node): """ Find inorder predecessor of node. Time: O(h) """ if node.left: curr = node.left while curr.right: curr = curr.right return curr parent = node.parent while parent and node == parent.left: node = parent parent = parent.parent return parentAVL Trees
Section titled “AVL Trees”An AVL tree (Adelson-Velsky and Landis, 1962) is a self-balancing BST where the balance factor Of every node is in . The balance factor is the height of the right subtree minus the Height of the left subtree.
Balance Factor
Section titled “Balance Factor”After every insertion or deletion, we walk back up from the modified node to the root, rebalancing as needed. The balance factor must be in for every node.
Rotations
Section titled “Rotations”graph TD
subgraph "Right Rotation (LL case)"
A["z (bf=-2)"] --> B["y (bf=-1)"]
A -.-> C["T3"]
B --> D["T1"]
B --> E["T2"]
endLL (Left-Left) Case: Right rotation on .
z y / \ / \ y T3 => T1 z / \ / \ T1 T2 T2 T3RR (Right-Right) Case: Left rotation on .
z y / \ / \ T1 y => z T3 / \ / \ T2 T3 T1 T2LR (Left-Right) Case: Left rotation on Then right rotation on .
z z x / \ / \ / \ y T3 => x T3 => y z / \ / \ / \ / \ T1 x y T2 T1 T2 T3 T4 / \ / \ T2 T3 T1 T2RL (Right-Left) Case: Right rotation on Then left rotation on .
z z x / \ / \ / \ T1 y => T1 x => z y / \ / \ / \ / \ x T4 T2 y T1 T2 T3 T4 / \ / \ T2 T3 T3 T4Implementation
Section titled “Implementation”class AVLNode: def __init__(self, key, val=None): self.key = key self.val = val self.left = None self.right = None self.height = 1
class AVLTree: """ AVL tree: self-balancing BST. Search: O(log n) worst case Insert: O(log n) worst case (at most 2 rotations) Delete: O(log n) worst case (at most O(log n) rotations) Space: O(n) """ def _height(self, node): return node.height if node else 0
def _balance_factor(self, node): if node is None: return 0 return self._height(node.right) - self._height(node.left)
def _update_height(self, node): node.height = 1 + max(self._height(node.left), self._height(node.right))
def _rotate_right(self, z): y = z.left T3 = y.right y.right = z z.left = T3 self._update_height(z) self._update_height(y) return y
def _rotate_left(self, z): y = z.right T2 = y.left y.left = z z.right = T2 self._update_height(z) self._update_height(y) return y
def _rebalance(self, node): self._update_height(node) bf = self._balance_factor(node)
if bf > 1: if self._balance_factor(node.right) < 0: node.right = self._rotate_right(node.right) return self._rotate_left(node) if bf < -1: if self._balance_factor(node.left) > 0: node.left = self._rotate_left(node.left) return self._rotate_right(node)
return node
def insert(self, root, key, val=None): if root is None: return AVLNode(key, val) if key < root.key: root.left = self.insert(root.left, key, val) elif key > root.key: root.right = self.insert(root.right, key, val) return self._rebalance(root)
def delete(self, root, key): if root is None: return None if key < root.key: root.left = self.delete(root.left, key) elif key > root.key: root.right = self.delete(root.right, key) else: if root.left is None: return root.right if root.right is None: return root.left successor = root.right while successor.left: successor = successor.left root.key = successor.key root.val = successor.val root.right = self.delete(root.right, successor.key) return self._rebalance(root)
def search(self, root, key): curr = root while curr: if key == curr.key: return curr elif key < curr.key: curr = curr.left else: curr = curr.right return NoneComplexity Proof
Section titled “Complexity Proof”An AVL tree with nodes has height at most . Proof sketch: the minimum Number of nodes in an AVL tree of height is with . This is closely related to the Fibonacci sequence, giving . Since We get for some constant .
| Operation | Worst Case | Rotations per Insert | Rotations per Delete |
|---|---|---|---|
| Search | 0 | 0 | |
| Insert | 0 | ||
| Delete | 0 |