C.W.K.
Stream
Lesson 05 of 07 · published

Balancing: Keeping the Tree Honest

~12 min · trees, balancing, avl, red-black

Level 0Curious Beginner
0 XP0/85 lessons0/19 achievements
0/100 XP to next level100 XP to go0% complete
"A plain BST is one sorted input away from collapsing into a linked list. Self-balancing trees refuse to let that happen — they quietly reshape themselves so the height can never explode."

The Fix: Trees That Rebalance Themselves

Last lesson's villain was the lopsided tree: insert sorted data, get an O(n) chain. Self-balancing trees solve it by automatically restructuring after each insert or delete, keeping the height pinned near log n no matter what order the data arrives in. The result is a hard guarantee: O(log n) search, insert, and delete — worst case, not just average. They turn the BST from "fast if you're lucky" into "fast, period."

The Mechanism: Rotations

The magic is a local operation called a rotation. When one side of the tree grows too tall, a rotation pivots a node and its child — promoting the child, demoting the parent — which reduces the height on the heavy side while preserving the BST invariant (left still smaller, right still larger). Rotations are O(1), purely local pointer rewiring, and a handful of them after any insert/delete is enough to keep the whole tree balanced. You don't need to memorize the rotation cases to use a balanced tree — but knowing that "rebalancing = a few O(1) rotations" demystifies how the guarantee is cheap to maintain.

Two Famous Flavors

  • AVL trees — strictly balanced (sibling heights differ by at most 1). Tighter balance means faster lookups but more rotations on writes. Good when reads dominate.
  • Red-black trees — loosely balanced (using node "colors" and rules), so they do fewer rotations on insert/delete at the cost of slightly taller trees. Good when writes are frequent — which is why they back most standard libraries: Java's TreeMap, C++'s std::map, and even parts of the Linux kernel.

And for data on disk (databases, filesystems), the variant is a B-tree: each node holds many keys, so the tree is short and wide — fewer levels means fewer slow disk reads per lookup. Every relational database index you've ever queried is almost certainly a B-tree.

Self-balancing trees use O(1) rotations after each insert/delete to keep height ~log n, guaranteeing O(log n) worst case. AVL = stricter balance (read-fast); red-black = looser (write-fast, library default); B-trees = wide nodes for disk (database indexes).

The Practical Python Reality

Here's a fact that surprises people: Python has no built-in balanced BST. No TreeMap, no std::map equivalent in the standard library. So when you genuinely need ordered operations in Python, you reach for: the bisect module (binary search and insertion into a sorted list — great when you read far more than you insert), or the third-party sortedcontainers library (a SortedList/SortedDict with O(log n) inserts). Knowing the theory tells you what you need; knowing the ecosystem tells you which tool actually delivers it.

Pippa's Confession

I went looking for Python's TreeMap and... there isn't one. I was indignant — every other language has a balanced tree in its standard library! Dad explained the design choice: Python's dict (hashing) is so good for the common case that ordered maps were left to bisect and third-party libs. The lesson wasn't about trees; it was that 'the right structure' includes 'what's actually available in your language' — theory and ecosystem are both part of the decision.

Code

A rotation, and Python's bisect substitute·python
# A right rotation, conceptually: rebalance while keeping BST order intact.
#     y            x
#    / \          / \
#   x   C   -->  A   y
#  / \              / \
# A   B            B   C
# Before: A < x < B < y < C.  After: A < x < B < y < C.  Order preserved!
class N:
    def __init__(s, k): s.k=k; s.left=None; s.right=None

def rotate_right(y):
    x = y.left
    y.left = x.right    # B moves under y
    x.right = y         # y becomes x's right child
    return x            # x is the new subtree root (shorter on the left now)

# The PRACTICAL Python answer for ordered ops: the bisect module on a sorted list.
import bisect
sorted_keys = [1, 3, 6, 8, 10]
bisect.insort(sorted_keys, 7)        # insert keeping sorted order: O(n) shift, O(log n) find
print(sorted_keys)                    # [1, 3, 6, 7, 8, 10]
i = bisect.bisect_left(sorted_keys, 7)
print("7 is at index", i)             # 3 — O(log n) search
# For heavy inserts, the third-party 'sortedcontainers' gives O(log n) inserts too.
# Python has NO built-in balanced BST — this is the idiomatic substitute.

External links

Exercise

Explain why a tree rotation can rebalance a subtree WITHOUT breaking the BST ordering invariant (hint: trace where the subtrees A, B, C land before and after). Then choose: for a read-heavy ordered map (lots of lookups, rare inserts) would you prefer an AVL tree or a red-black tree, and why?
Hint
A rotation only re-parents nodes; the relative order A < x < B < y < C is identical before and after, so search still works. Read-heavy → AVL: its stricter balance means shallower trees and faster lookups, and you rarely pay its higher rotation cost since inserts are rare.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.