跳转到内容

Binary Search Trees and Balanced Trees

此内容尚不支持你的语言。

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.

A binary search tree (BST) is a binary tree where every node satisfies the BST property: for any Node with key kk All keys in its left subtree are strictly less than kk And all keys in its Right subtree are strictly greater than kk.

class BSTNode:
def __init__(self, key, val=None):
self.key = key
self.val = val
self.left = None
self.right = None
self.parent = None
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 None
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 root
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 node
graph 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]
    end
ShapeHeightSearch/Insert/Delete
BalancedO(log⁡n)O(\log n)O(log⁡n)O(\log n)
DegenerateO(n)O(n)O(n)O(n)
RandomO(log⁡n)O(\log n) expectedO(log⁡n)O(\log n) expected

For nn distinct keys inserted in random order, the expected height of a BST is approximately 2ln⁡n≈1.39log⁡2n2 \ln n \approx 1.39 \log_2 n. This is only about 39% taller than a perfectly balanced tree, but the worst case (sorted input) gives height nn.

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)
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 parent

An AVL tree (Adelson-Velsky and Landis, 1962) is a self-balancing BST where the balance factor Of every node is in {−1,0,1}\{-1, 0, 1\}. The balance factor is the height of the right subtree minus the Height of the left subtree.

\mathrm{bf(v) = \mathrm{height(v.\mathrm{right) - \mathrm{height(v.\mathrm{left)

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 {−1,0,1}\{-1, 0, 1\} for every node.

graph TD
    subgraph "Right Rotation (LL case)"
        A["z (bf=-2)"] --> B["y (bf=-1)"]
        A -.-> C["T3"]
        B --> D["T1"]
        B --> E["T2"]
    end

LL (Left-Left) Case: Right rotation on zz.

z y
/ \ / \
y T3 => T1 z
/ \ / \
T1 T2 T2 T3

RR (Right-Right) Case: Left rotation on zz.

z y
/ \ / \
T1 y => z T3
/ \ / \
T2 T3 T1 T2

LR (Left-Right) Case: Left rotation on yy Then right rotation on zz.

z z x
/ \ / \ / \
y T3 => x T3 => y z
/ \ / \ / \ / \
T1 x y T2 T1 T2 T3 T4
/ \ / \
T2 T3 T1 T2

RL (Right-Left) Case: Right rotation on yy Then left rotation on zz.

z z x
/ \ / \ / \
T1 y => T1 x => z y
/ \ / \ / \ / \
x T4 T2 y T1 T2 T3 T4
/ \ / \
T2 T3 T3 T4
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 None

An AVL tree with nn nodes has height at most 1.44log⁡2(n+2)−1.3281.44 \log_2(n+2) - 1.328. Proof sketch: the minimum Number of nodes in an AVL tree of height hh is N(h)=N(h−1)+N(h−2)+1N(h) = N(h-1) + N(h-2) + 1 with N(0)=1N(0) = 1 N(1)=2N(1) = 2. This is closely related to the Fibonacci sequence, giving N(h)=Fh+3−1N(h) = F_{h+3} - 1. Since Fk≈ϕk/5F_k \approx \phi^k / \sqrt{5} We get h≤clog⁡ϕ(n)h \le c \log_\phi(n) for some constant cc.

OperationWorst CaseRotations per InsertRotations per Delete
SearchO(log⁡n)O(\log n)00
InsertO(log⁡n)O(\log n)≤2\le 20
DeleteO(log⁡n)O(\log n)0O(log⁡n)O(\log n)