Balanced trees: AVL, red-black and B-trees
See how self-balancing trees keep their height at O(log n): balance factors and rotations in AVL trees, the rules of red-black trees, and why databases use wide B-trees.
Rotations: reshaping without breaking the order
Y X / \ rotate / \ X C right A Y / \ ───→ / \ A B B C in-order before: A X B Y C in-order after: A X B Y CAVL trees
| Case | Where the new key went | Fix |
|---|---|---|
var tree = new AvlTree();for (int i = 1; i <= 1000; i++) tree.Insert(i); // sorted: worst case for a BSTConsole.WriteLine($"1,000 sorted inserts: height {tree.Height}");for (int i = 1001; i <= 1_000_000; i++) tree.Insert(i);Console.WriteLine($"1,000,000 sorted inserts: height {tree.Height}"); class AvlTree{ private class Node { public int Key, Height = 1; // height counted in nodes here public Node? Left, Right; public Node(int key) => Key = key; } private Node? _root; public int Height => H(_root) - 1; // in edges, like the rest of the course private static int H(Node? n) => n?.Height ?? 0; private static void Update(Node n) => n.Height = 1 + Math.Max(H(n.Left), H(n.Right)); private static int Balance(Node n) => H(n.Left) - H(n.Right); private static Node RotateRight(Node y) { var x = y.Left!; y.Left = x.Right; x.Right = y; Update(y); Update(x); return x; // x is the new subtree root } private static Node RotateLeft(Node x) { var y = x.Right!; x.Right = y.Left; y.Left = x; Update(x); Update(y); return y; } public void Insert(int key) => _root = Insert(_root, key); private static Node Insert(Node? n, int key) { if (n is null) return new Node(key); if (key < n.Key) n.Left = Insert(n.Left, key); else if (key > n.Key) n.Right = Insert(n.Right, key); else return n; Update(n); int b = Balance(n); if (b > 1 && key < n.Left!.Key) return RotateRight(n); // left-left if (b < -1 && key > n.Right!.Key) return RotateLeft(n); // right-right if (b > 1) { n.Left = RotateLeft(n.Left!); return RotateRight(n); } // left-right if (b < -1) { n.Right = RotateRight(n.Right!); return RotateLeft(n); } // right-left return n; }}1,000 sorted inserts: height 9 1,000,000 sorted inserts: height 19
Red-black trees
The root is black, and the empty (null) leaves count as black. A red node never has a red child. Every path from a node down to an empty leaf passes through the same number of black nodes.
SortedSet and SortedDictionary, Java’s TreeMap, and C++’s std::map.
B-trees: balanced trees for disks
| Tree | Height guarantee | Strength | Where you meet it |
|---|---|---|---|
SortedSet, SortedDictionary, TreeMap | |||
IsBalanced(Node? root): true if, at every node, the heights of the left and right subtrees differ by at most 1. Do it in O(n): do not call a separate height function for every node (that is O(n²) on a chain). A 3-node tree 2 / 1 3 is balanced; the chain 1 → 2 → 3 (all right children) is not.
Show a hint
Show the solution
var perfect = new Node(2, new Node(1), new Node(3));var chain = new Node(1, null, new Node(2, null, new Node(3))); Console.WriteLine(IsBalanced(perfect));Console.WriteLine(IsBalanced(chain)); static bool IsBalanced(Node? root) => CheckedHeight(root) != -1; static int CheckedHeight(Node? n) // height in nodes, or -1 if unbalanced{ if (n is null) return 0; int left = CheckedHeight(n.Left); if (left == -1) return -1; int right = CheckedHeight(n.Right); if (right == -1) return -1; if (Math.Abs(left - right) > 1) return -1; return 1 + Math.Max(left, right);} record Node(int Key, Node? Left = null, Node? Right = null);True False
Remember
Self-balancing trees keep height O(log n) whatever order keys arrive in. Rotations reshape a subtree in O(1) without changing the in-order sequence. AVL: balance factor in {−1, 0, +1}, fixed with single or double rotations. Red-black: colour rules, height ≤ 2 log₂ n, used by SortedSet/SortedDictionary. B-trees store hundreds of keys per node to minimise disk reads; they power database indexes.