<style> .reveal-up, .reveal-left, .reveal-right, .reveal-stagger > * { opacity: 1 !important; transform: none !important; } </style>
  1. Learn
  2. DSA
  3. Trees
All lessons in this course

Lesson 28 of 50 15 minIntermediate

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.

The last lesson ended with a problem: a BST is only fast while it is short, and real data (sorted IDs, timestamps) easily makes it tall. A self-balancing tree fixes its shape automatically after every insert and delete, so its height always stays O(log n), whatever order the keys arrive in.

Rotations: reshaping without breaking the order

The basic tool is a rotation. A right rotation around node Y lifts its left child X up: X becomes the parent, Y becomes X’s right child, and X’s old right subtree moves across to become Y’s left subtree. The in-order sequence of keys does not change, so the BST rule still holds, but one side gets one level shorter. A left rotation is the mirror image. A rotation only changes a few pointers: O(1).

Rotation.txt
      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 C

AVL trees

The AVL tree (1962, named after Adelson-Velsky and Landis) was the first self-balancing BST. Every node stores its height. Its balance factor is height(left) − height(right). The rule: every balance factor must be −1, 0 or +1. After an insert, walk back up the path; the first node whose factor became ±2 is fixed with one of four cases:

CaseWhere the new key wentFix
Left-leftLeft subtree of the left childOne right rotation
Right-rightRight subtree of the right childOne left rotation
Left-rightRight subtree of the left childLeft rotation on the child, then right rotation
Right-leftLeft subtree of the right childRight rotation on the child, then left rotation
Interactive

An AVL tree rebalancing itself

The small number beside each node is its balance factor: height(left) − height(right). AVL keeps every factor at −1, 0 or +1. When an insert makes one ±2, a rotation fixes it.

Sequence
empty tree
AVL height 0 · a plain BST with the same inserts would have height 0
Press Insert to add the first key. Watch the balance factors.
AvlTree.cs
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;    }}
Output
1,000 sorted inserts: height 9
1,000,000 sorted inserts: height 19

A plain BST would have heights 999 and 999,999 for the same input (and would overflow the stack long before that). The AVL tree stays at about log₂ n.

Red-black trees

A red-black tree colours every node red or black and keeps these rules:

  • 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.

Together these guarantee that the longest path is at most twice the shortest, so the height is at most about 2 log₂ n. That is a looser balance than AVL, so lookups can be slightly slower, but inserts and deletes need fewer rotations (at most 2 for an insert, 3 for a delete). That trade-off is why most libraries choose red-black trees: .NET’s SortedSet and SortedDictionary, Java’s TreeMap, and C++’s std::map.

B-trees: balanced trees for disks

Reading from disk or SSD is slow compared with memory, and data comes in pages (often 4 or 8 KB). A B-tree makes each node one page holding hundreds of keys and hundreds of children. With 500 children per node, three levels hold 500³ = 125 million keys, so any key is found in 3 or 4 page reads. All leaves are at the same depth, and nodes split or merge to stay balanced.

Almost every database index is a B-tree (more exactly a B+ tree, where all values live in the leaves and leaves are linked for fast range scans): SQL Server, PostgreSQL, SQLite, as well as file systems such as NTFS. When you create an index on a column, you are building one.

TreeHeight guaranteeStrengthWhere you meet it
Plain BSTnone (up to n)simpleteaching, random data
AVL≈ 1.44 log₂ nfastest lookupsread-heavy in-memory indexes
Red-black≤ 2 log₂ ncheap inserts and deletesSortedSet, SortedDictionary, TreeMap
B-tree / B+ treelog base ~hundredsfew disk readsdatabase indexes, file systems
Quick check

A node’s left subtree has height 3 and its right subtree height 1 (counted the same way). What is its balance factor, and is it allowed in an AVL tree?

Quick check

Why do databases use B-trees instead of red-black trees for indexes?

Practice

Write 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

Write one recursive function that returns the height, or −1 to mean “this subtree is not balanced”. As soon as a child returns −1, pass −1 straight up.

Show the solution
Solution.cs
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);
Output
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.
0 of 50 lessons completed