SwadhyAI Notes

All DSA notes

DSA Trees

AVL Trees

You insert keys 1…n into a plain BST. The tree becomes a stick; every lookup is linear. Dual conflict: the search-tree ordering is correct; the height is not. AVL keeps a of at most 1 in absolute value.

After each update, walk ancestors and when |BF|=2, preserving the . Height stays Θ(log n) by a Fibonacci-style recurrence.

Algorithm journey

1

Ordinary BST insert/delete

Then climb parents updating height.

2

Detect |BF|=2 on the path.

3

LL / LR / RR / RL

One or two rotations repair locally.

4

Public API returns

Every node has |BF|≤1 again.

What actually happens

Naive idea: use an unbalanced BST. Breaks on sorted inserts: height n, Θ(n) search.

Better idea: store heights, enforce |BF|≤1 with rotations. Insert needs at most one single/double rebalance; delete may walk further up.

The improved design still fails if height conventions mix (null=0 vs −1), if you rotate without updating heights/parents, or if you need fewer rotations under heavy updates (then red-black may win constants).

Invariant

After every public operation, the structure is a BST and every node has balance factor in {-1, 0, +1}. Stored heights match 1 + max(child heights) under a single null convention.

How to choose AVL

1. Lookup-heavy ordered map? AVL's tighter balance helps.

2. Update-heavy library default? Red-black often rotates less.

3. Mostly static? Sorted array + binary search may beat both.

Quantitative check

Fibonacci bound: n ≥ F_{h} style growth implies height ≤ ~1.44 log₂(n+2). At n=10⁶, AVL height is tens of comparisons, not a million.

Ordering

BST invariant

Every subtree still owns a key interval.

Balance

|BF| ≤ 1

At every reachable node.

Worst height

Θ(log n)

Proved by a Fibonacci-like recurrence.

Fix one height convention and use it everywhere

These notes use height(null) = 0 and height(leaf) = 1. For a non-null node,height = 1 + max(height(left), height(right)). Define the balance factor as BF(v) = height(v.left) − height(v.right); positive means left-heavy and negative means right-heavy. The alternative null = −1, leaf = 0 convention is equally valid, but mixing conventions creates off-by-one bugs.

1

BST order

All keys respect the tree's explicit duplicate policy and ancestor bounds.

Short takeaway

AVL is a BST that keeps |balance factor| ≤ 1 via rotations, forcing Θ(log n) height. Pick a single height convention and update metadata after every rotation.

Exercises

  1. Insert 1…7 into an empty AVL. Which rotation fires first, and what is BF at the new root?
  2. Why does |BF|=2 with a zigzag child (LR) need two rotations?
  3. n=10⁶ sorted inserts: compare plain BST height vs AVL height bound ~1.44 log₂ n.

Primary sources

  • Adelson-Velsky & Landis (1962): An algorithm for the organization of information.
  • Cormen et al., CLRS: balanced search trees / rotation mechanics.
  • Knuth, The Art of Computer Programming Vol. 3: balanced trees.
2

Stored height

Every node's height exactly matches 1 + max of its children's heights.

Short takeaway

AVL is a BST that keeps |balance factor| ≤ 1 via rotations, forcing Θ(log n) height. Pick a single height convention and update metadata after every rotation.

Exercises

  1. Insert 1…7 into an empty AVL. Which rotation fires first, and what is BF at the new root?
  2. Why does |BF|=2 with a zigzag child (LR) need two rotations?
  3. n=10⁶ sorted inserts: compare plain BST height vs AVL height bound ~1.44 log₂ n.

Primary sources

  • Adelson-Velsky & Landis (1962): An algorithm for the organization of information.
  • Cormen et al., CLRS: balanced search trees / rotation mechanics.
  • Knuth, The Art of Computer Programming Vol. 3: balanced trees.
3

AVL balance

Every node has balance factor −1, 0, or +1 after a public operation.

Short takeaway

AVL is a BST that keeps |balance factor| ≤ 1 via rotations, forcing Θ(log n) height. Pick a single height convention and update metadata after every rotation.

Exercises

  1. Insert 1…7 into an empty AVL. Which rotation fires first, and what is BF at the new root?
  2. Why does |BF|=2 with a zigzag child (LR) need two rotations?
  3. n=10⁶ sorted inserts: compare plain BST height vs AVL height bound ~1.44 log₂ n.

Primary sources

  • Adelson-Velsky & Landis (1962): An algorithm for the organization of information.
  • Cormen et al., CLRS: balanced search trees / rotation mechanics.
  • Knuth, The Art of Computer Programming Vol. 3: balanced trees.
4

Metadata

Parent, size, count, or aggregate fields agree with the rotated child links.

Short takeaway

AVL is a BST that keeps |balance factor| ≤ 1 via rotations, forcing Θ(log n) height. Pick a single height convention and update metadata after every rotation.

Exercises

  1. Insert 1…7 into an empty AVL. Which rotation fires first, and what is BF at the new root?
  2. Why does |BF|=2 with a zigzag child (LR) need two rotations?
  3. n=10⁶ sorted inserts: compare plain BST height vs AVL height bound ~1.44 log₂ n.

Primary sources

  • Adelson-Velsky & Landis (1962): An algorithm for the organization of information.
  • Cormen et al., CLRS: balanced search trees / rotation mechanics.
  • Knuth, The Art of Computer Programming Vol. 3: balanced trees.

BF = +1

left subtree is one level taller

BF = 0

subtrees have equal height

BF = −1

right subtree is one level taller

Interactive: insert into an AVL tree

This original visualization shows rotations occurring during live insertion. Insert ascending or descending keys and compare the shape with a plain BST built from the same sequence.

ℹ️ Ready to build an AVL Tree!
Empty AVL Tree

Interactive: derive LL, RR, LR, and RL repairs

Switch among the four imbalance shapes. Track the balance factor, the subtree root returned by each rotation, the preserved inorder sequence, and why the demoted node's height must be updated before the promoted node's height.

AVL rotation laboratory

Rotations change shape, not inorder order

insert 30, 20, 10

one right rotation at 30

Step 1 of 3

Insertion creates an LL imbalance

BF = h(left) − h(right)
30h=3 · BF=+220h=2 · BF=+110h=1 · BF=0

Pointer change

Key 10 entered the left subtree of 20, which is itself the left child of 30. Node 30 now has BF = +2.

Ordering proof

10 < 20 < 30

Height update order

Heights were recomputed on the return path: 10, then 20, then 30.

Result

The first unbalanced ancestor is 30.

Keyboard: focus within the demo, then use ← →, Home, or End.

A rotation is an order-preserving local rewrite

Right rotation around y

x = y.left

Promote x, set x.right = y, and move the middle subtreeT₂ = x.right (before rewiring) to y.left. The key intervals remainT₁ < x < T₂ < y < T₃.

Left rotation around x

y = x.right

Promote y, set y.left = x, and move the middle subtreeT₂ = y.left (before rewiring) to x.right. This is the exact mirror operation.

Height-update dependency

After a right rotation, update demoted y first; promoted x now depends on the new height ofy. After a left rotation, update demoted x first, then promoted y. Reversing this order can leave a plausible but stale height that breaks a later rebalance decision.

Classify imbalance from subtree balance, not from names alone

AVL imbalance cases, balance-factor tests, and rotations
CaseBalance-factor testRepairDetail
LLBF(z) = +2 and BF(z.left) ≥ 0rightRotate(z)One right rotation; child BF can be 0 after deletion.
LRBF(z) = +2 and BF(z.left) < 0z.left = leftRotate(z.left); rightRotate(z)First straighten the zigzag, then rotate z.
RRBF(z) = −2 and BF(z.right) ≤ 0leftRotate(z)One left rotation; child BF can be 0 after deletion.
RLBF(z) = −2 and BF(z.right) > 0z.right = rightRotate(z.right); leftRotate(z)Mirror of LR.

In insertion-only explanations, the newly inserted key can identify LL/RR/LR/RL. That shortcut is unsafe for deletion because the removed key is no longer on the heavy path. Inspect child balance factors instead.

Insertion: BST update, height update, rebalance

Descend

Use ordinary BST comparisons until a null child becomes the new leaf; this example rejects duplicate keys.

Unwind

For each ancestor, recompute height from already-correct child heights, then inspect its balance factor.

Repair

Return the root produced by zero, one, or two rotations and assign it to the parent's child link.

Stop rotations

After insertion, repairing the first unbalanced ancestor restores that subtree's prior height, so higher ancestors need no rotation.

Python

python
class Node:
    def __init__(self, key):
        self.key = key
        self.left = None
        self.right = None
        self.height = 1


def height(node):
    return 0 if node is None else node.height


def update(node):
    node.height = 1 + max(height(node.left), height(node.right))


def balance(node):
    return height(node.left) - height(node.right)


def rotate_right(y):
    x = y.left
    middle = x.right
    x.right = y
    y.left = middle
    update(y)                 # demoted first
    update(x)
    return x


def rotate_left(x):
    y = x.right
    middle = y.left
    y.left = x
    x.right = middle
    update(x)                 # demoted first
    update(y)
    return y


def rebalance(node):
    update(node)
    bf = balance(node)
    if bf > 1:
        if balance(node.left) < 0:
            node.left = rotate_left(node.left)
        return rotate_right(node)
    if bf < -1:
        if balance(node.right) > 0:
            node.right = rotate_right(node.right)
        return rotate_left(node)
    return node


def insert(node, key):
    if node is None:
        return Node(key)
    if key < node.key:
        node.left = insert(node.left, key)
    elif key > node.key:
        node.right = insert(node.right, key)
    else:
        return node             # reject duplicate
    return rebalance(node)

Java

java
final class AvlTree {
static final class Node {
    int key, height = 1;
    Node left, right;
    Node(int key) { this.key = key; }
}

static int height(Node node) {
    return node == null ? 0 : node.height;
}

static void update(Node node) {
    node.height = 1 + Math.max(height(node.left), height(node.right));
}

static int balance(Node node) {
    return height(node.left) - height(node.right);
}

static Node rotateRight(Node y) {
    Node x = y.left;
    Node middle = x.right;
    x.right = y;
    y.left = middle;
    update(y);                 // demoted first
    update(x);
    return x;
}

static Node rotateLeft(Node x) {
    Node y = x.right;
    Node middle = y.left;
    y.left = x;
    x.right = middle;
    update(x);                 // demoted first
    update(y);
    return y;
}

static Node rebalance(Node node) {
    update(node);
    int bf = balance(node);
    if (bf > 1) {
        if (balance(node.left) < 0) {
            node.left = rotateLeft(node.left);
        }
        return rotateRight(node);
    }
    if (bf < -1) {
        if (balance(node.right) > 0) {
            node.right = rotateRight(node.right);
        }
        return rotateLeft(node);
    }
    return node;
}

static Node insert(Node node, int key) {
    if (node == null) return new Node(key);
    if (key < node.key) {
        node.left = insert(node.left, key);
    } else if (key > node.key) {
        node.right = insert(node.right, key);
    } else {
        return node;           // reject duplicate
    }
    return rebalance(node);
}
}

Deletion can propagate imbalance to the root

First perform ordinary BST deletion. Removing a node can shorten a subtree. Even after one ancestor is rotated, the repaired subtree may remain one level shorter than before, so its parent can become unbalanced too. Continue recomputing and rebalancing on the entire return path. There can be O(log n) rebalanced ancestors, with at most a constant number of rotations at each.

Python

python
# Uses Node and rebalance from the insertion implementation.
def delete(node, key):
    if node is None:
        return None

    if key < node.key:
        node.left = delete(node.left, key)
    elif key > node.key:
        node.right = delete(node.right, key)
    else:
        if node.left is None:
            return node.right
        if node.right is None:
            return node.left

        successor = node.right
        while successor.left is not None:
            successor = successor.left
        node.key = successor.key
        node.right = delete(node.right, successor.key)

    return rebalance(node)     # every surviving ancestor

Java

java
final class AvlDelete {
static final class Node {
    int key, height = 1;
    Node left, right;
    Node(int key) { this.key = key; }
}

static int height(Node node) {
    return node == null ? 0 : node.height;
}

static void update(Node node) {
    node.height = 1 + Math.max(height(node.left), height(node.right));
}

static int balance(Node node) {
    return height(node.left) - height(node.right);
}

static Node rotateRight(Node y) {
    Node x = y.left;
    Node middle = x.right;
    x.right = y;
    y.left = middle;
    update(y);
    update(x);
    return x;
}

static Node rotateLeft(Node x) {
    Node y = x.right;
    Node middle = y.left;
    y.left = x;
    x.right = middle;
    update(x);
    update(y);
    return y;
}

static Node rebalance(Node node) {
    update(node);
    int bf = balance(node);
    if (bf > 1) {
        if (balance(node.left) < 0) node.left = rotateLeft(node.left);
        return rotateRight(node);
    }
    if (bf < -1) {
        if (balance(node.right) > 0) node.right = rotateRight(node.right);
        return rotateLeft(node);
    }
    return node;
}

static Node delete(Node node, int key) {
    if (node == null) return null;

    if (key < node.key) {
        node.left = delete(node.left, key);
    } else if (key > node.key) {
        node.right = delete(node.right, key);
    } else {
        if (node.left == null) return node.right;
        if (node.right == null) return node.left;

        Node successor = node.right;
        while (successor.left != null) {
            successor = successor.left;
        }
        node.key = successor.key;
        node.right = delete(node.right, successor.key);
    }

    return rebalance(node);    // every surviving ancestor
}
}

Insertion propagation

Heights may change all the way upward, but the first rotation (single or double) restores the affected subtree's pre-insertion height. At most one ancestor needs a structural rebalance.

Deletion propagation

A repaired subtree can still be shorter, so another ancestor may cross ±1. Continue to the root; choosing a case from child balance also handles a child BF of 0.

Why the height is logarithmic

Let N(h) be the fewest nodes in an AVL tree of height h under the leaf-height-1 convention. To make a height-h tree as sparse as possible while remaining balanced, one child has height h − 1 and the other h − 2:

N(0) = 0, N(1) = 1, N(h) = 1 + N(h − 1) + N(h − 2)

This grows like the Fibonacci sequence, exponentially in h. Inverting the relationship gives h = O(log n). Since a binary tree can also require Ω(log n) height, AVL height is Θ(log n). A path operation and its recursive stack both use O(log n) space/time depth in the worst case; search itself can be iterative with Θ(1) auxiliary space.

Search

Θ(log n) worst

No mutation or rotation.

Insert

Θ(log n) worst

Path plus at most one single/double rebalance.

Delete

Θ(log n) worst

May rebalance multiple ancestors.

Audit all invariants in one postorder pass

A useful test helper returns facts upward rather than recomputing height repeatedly. It checks strict BST bounds, derives the actual height, compares it with the stored value, and rejects a balance factor outside −1…1.

Language-neutral pseudocode

Terminal
audit(node, low, high):
if node is null:
return (valid=true, actualHeight=0, nodeCount=0)
 
if not (low < node.key < high):
return (valid=false, actualHeight=0, nodeCount=0)
 
L = audit(node.left, low, node.key)
if not L.valid:
return L
 
R = audit(node.right, node.key, high)
if not R.valid:
return R
 
actual = 1 + max(L.actualHeight, R.actualHeight)
 
valid = node.height == actual
and abs(L.actualHeight - R.actualHeight) <= 1
 
return (valid, actual, 1 + L.nodeCount + R.nodeCount)

Run this after randomized insert/delete sequences in tests. Comparing inorder output with a trusted sorted set catches membership errors; the structural audit catches stale heights and balance violations that output alone cannot see.

Trade-offs: balance is not free

AVL, other trees, and sorted-array trade-offs
StructureHeight / lookupUpdate workTypical fit
Plain BSTNo guarantee; worst Θ(n)No rotationsSimple or externally randomized/controlled workloads
AVL treeWorst Θ(log n), tightly balancedMay rotate and update heightsLookup-heavy ordered sets/maps with strict latency bounds
Red-black treeWorst Θ(log n), looser balanceOften fewer rotations on updatesGeneral ordered-library containers with frequent updates
Sorted arrayBinary search Θ(log n)Interior update Θ(n)Mostly static data and cache-friendly scans

AVL nodes need at least height or balance metadata. Rotations also require pointer writes and careful updates to parent links and augmentations. Tighter height can improve lookup path length, while red-black trees often trade slightly taller paths for less rebalancing in update-heavy workloads. Actual performance also depends on allocation and cache locality.

Pitfalls that corrupt the next operation

Mixing height conventions

Choose null/leaf base heights once; derive update, BF, and tests from that convention.

Updating the promoted node first

The promoted height depends on the demoted node after rewiring; update demoted, then promoted.

Dropping the middle subtree

Save T₂ before changing child pointers and attach it to the demoted pivot.

Ignoring the returned subtree root

Every insert, delete, and rotation can return a new root; assign it into the parent link and top-level root.

Using inserted/deleted key to classify deletion

After deletion, inspect the heavy child's balance factor; the removed key does not identify the shape.

Stopping after one deletion rotation

A deletion can shorten the repaired subtree, so keep rebalancing through every ancestor.

Undefined duplicate policy

Counts preserve strict structural order cleanly; side-placement policies may conflict with rotations.

Updating height but not other metadata

Rotations must also repair parent pointers, subtree sizes, sums, or application-specific aggregates.

Decision rule

Choose AVL when you need a dynamic ordered set/map with strict worst-case logarithmic paths and lookup latency matters enough to justify tighter balance and more intricate updates. Use a plain BST only when its shape is controlled or worst-case degradation is acceptable; consider red-black trees for update-heavy general containers, B/B+ trees for block storage, hash tables for unordered exact lookup, and sorted arrays for mostly static data.