DSA DSA In Depth
0%
lesson avl-treerotationsbalanced-bst

Tree Rotations & AVL Trees: Balance Factors, LL/RR/LR/RL Rebalancing & Fibonacci Height Bound

7 min read Module 3.2: Self-Balancing, Augmented, Disk-Backed & Persistent Trees Updated Oct 23, 2026
📄 Cheat Sheet
AVL Trees: Balance Factors and LL, RR, LR, RL Rotations
Abdul Bari AVL Trees: Balance Factors and LL, RR, LR, RL Rotations Watch on YouTube ↗

Traces height-balance factor updates and demonstrates every single and double rotation (LL, RR, LR, RL) required to restore the |bf| <= 1 invariant in O(1) time.

0:00 / --:--

Local Algebra of Rotations & The Four AVL Rebalancing Cases

Define the height of a null subtree as $-1$ and a leaf as $0$, so $h(u) = 1 + \max(h(u.\text{left}), h(u.\text{right}))$. The balance factor is $\text{bf}(u) = h(u.\text{left}) - h(u.\text{right}) \in \{-1, 0, +1\}$. When an insertion or deletion pushes $\text{bf}(z)$ to $+2$ (left-heavy) or $-2$ (right-heavy) at the lowest unbalanced ancestor $z$, let $y$ be the taller child of $z$ and $x$ be the taller child of $y$:

  • Left-Left (LL, Outer): $\text{bf}(z) = +2$ and $\text{bf}(y) \ge 0$. A single rotateRight(z) restores balance and reduces the subtree height by $1$.
  • Right-Right (RR, Outer): $\text{bf}(z) = -2$ and $\text{bf}(y) \le 0$. A single rotateLeft(z) restores balance.
  • Left-Right (LR, Inner Zig-Zag): $\text{bf}(z) = +2$ and $\text{bf}(y) = -1$. A single right rotation on $z$ fails because the heavy grandchild $x = y.\text{right}$ stays at the same depth! We must perform a double rotation: rotateLeft(y) followed by rotateRight(z), lifting $x$ to the root of the subtree.
  • Right-Left (RL, Inner Zig-Zag): $\text{bf}(z) = -2$ and $\text{bf}(y) = +1$. Execute rotateRight(y) followed by rotateLeft(z).
AVL Operation Time Complexity Max Rotations (Worst-Case) Height / Comparison Bound
search(key) O(log N) 0 $\le 1.4404 \log_2(N + 2) - 0.328$
insert(key) O(log N) ≤ 1 Single or 1 Double (2 rots) Subtree height returns to pre-insert value!
erase(key) O(log N) O(log N) Rotations Up Spine At most 1 rotation per ancestor level

Fibonacci Tree Extremal Proof: Why $h < 1.4404 \log_2(N + 2)$

What is the minimum number of nodes $N_h$ required to construct a valid AVL tree of height $h$? To minimize nodes at height $h$, the root must have one subtree of height $h - 1$ and the other of height $h - 2$ (the maximum legal imbalance). By induction:

$$N_0 = 1, \quad N_1 = 2, \quad N_h = 1 + N_{h-1} + N_{h-2}$$

Adding $1$ to both sides yields $(N_h + 1) = (N_{h-1} + 1) + (N_{h-2} + 1)$, which is the exact Fibonacci recurrence! Since $N_0 + 1 = 2 = F_3$ and $N_1 + 1 = 3 = F_4$, we have the exact identity:

$$N \ge N_h = F_{h+3} - 1 > \frac{\phi^{h+3}}{\sqrt{5}} - 2 \implies h < \frac{1}{\log_2 \phi} \log_2(N + 2) - 0.3277 \approx 1.4404 \log_2(N + 2)$$

Crucial Insert vs. Delete Asymmetry: On AVL insert, the first unbalanced ancestor $z$ had height $k+1$ before insertion, grew to $k+2$, and after 1 single or double rotation shrinks back to $k+1$—so at most 1 rebalancing event ever occurs on insertion! On AVL delete, shrinking $z$ from $k+2$ to $k+1$ via rotation reduces the subtree's height as seen by $z$'s parent, which can cascade $O(\log N)$ rotations all the way to the root.

Self-Balancing BST: AVL Balance Factors & LLRB 2-3-4 Rotations
Height ≤ 1.44 log₂N (AVL) / 2 log₂N (RB)

Trace how AVL LL/LR/RR/RL rotations and Sedgewick Left-Leaning Red-Black (LLRB) local transformations (rotateLeft, rotateRight, flipColors) preserve in-order sequence and subtree sizes while bounding tree height.

Step 1 / 10
Custom Input:
Timeline

Invariant & State

Inserted 20: Red right-leaning link on 10 -> violates LLRB left-leaning invariant!
10(B) 20(R)
Operation
Insert 20 (Red)
Rebalance Action
Pending rotateLeft(10)
Black-Height (bh)
1
In-Order Keys
[10, 20, 30]
[Step 1] Inserted 20: Red right-leaning link on 10 -> violates LLRB left-leaning invariant!
1. LR Imbalance: bf(z)=+2, bf(y)=-1 z y x 2. rotateLeft(y) → LL Shape z x y 3. rotateRight(z) → Balanced! x y z In-Order Preserved: α < y < β₁ < x < β₂ < z < γ Extremal Fibonacci Recurrence: N_h = 1 + N_{h-1} + N_{h-2} = F_{h+3} - 1 ⇒ h < 1.4404 log₂(N+2)
flowchart TD Check["Update height(u); bf = height(u.L) - height(u.R)"] --> Cases{"|bf| <= 1 ?"} Cases -- "Yes" --> Done["Balanced: return u"] Cases -- "bf == +2 (Left Heavy)" --> LCheck{"bf(u.L) < 0 ?"} LCheck -- "No (LL Case)" --> LL["return rotateRight(u)"] LCheck -- "Yes (LR Case)" --> LR["u.L = rotateLeft(u.L) return rotateRight(u)"] Cases -- "bf == -2 (Right Heavy)" --> RCheck{"bf(u.R) > 0 ?"} RCheck -- "No (RR Case)" --> RR["return rotateLeft(u)"] RCheck -- "Yes (RL Case)" --> RL["u.R = rotateRight(u.R) return rotateLeft(u)"]

Hardware Reality: Storing uint8_t height vs 2-Bit Balance Factor in Pointer Low Bits

Because $h < 1.44 \log_2(2^{64}) \approx 92$ for any tree that can ever fit in a 64-bit address space, storing a uint8_t height field requires only 1 byte. Better yet, because 64-bit heap allocations are at least 8-byte aligned, the lowest 3 bits of node->parent are always zero (0b000). Production C libraries (like libavl) pack the 2-bit balance factor $\text{bf} \in \{-1, 0, +1\}$ directly into the low 2 bits of the parent pointer, achieving zero bytes of structural alignment padding!

Implementation: Clean AVL Rotations & Rebalance Engine

// C++20: Self-Balancing AVL Tree Core with LL, RR, LR, RL Rebalancing
#include <algorithm>
#include <cstdint>

struct AVLNode {
    int key;
    int32_t height = 0; // Leaf has height 0; nullptr has height -1
    AVLNode *left = nullptr, *right = nullptr;
    explicit AVLNode(int k) : key(k) {}
};

inline int32_t h(const AVLNode* n) noexcept { return n ? n->height : -1; }
inline int32_t bf(const AVLNode* n) noexcept { return n ? (h(n->left) - h(n->right)) : 0; }
inline void pullHeight(AVLNode* n) noexcept { n->height = 1 + std::max(h(n->left), h(n->right)); }

AVLNode* rotateRight(AVLNode* z) noexcept {
    AVLNode* y = z->left;
    z->left = y->right;
    y->right = z;
    pullHeight(z); // Must update lower node z BEFORE new root y!
    pullHeight(y);
    return y;
}

AVLNode* rotateLeft(AVLNode* z) noexcept {
    AVLNode* y = z->right;
    z->right = y->left;
    y->left = z;
    pullHeight(z);
    pullHeight(y);
    return y;
}

AVLNode* rebalance(AVLNode* u) noexcept {
    pullHeight(u);
    int32_t balance = bf(u);
    if (balance > 1) { // Left-heavy (+2)
        if (bf(u->left) < 0) u->left = rotateLeft(u->left); // LR -> LL
        return rotateRight(u);
    }
    if (balance < -1) { // Right-heavy (-2)
        if (bf(u->right) > 0) u->right = rotateRight(u->right); // RL -> RR
        return rotateLeft(u);
    }
    return u;
}

Failure Modes & Adversarial Pitfalls

  • Updating Heights in the Wrong Order After Rotation: Inside rotateRight(z) (which lifts $y = z.\text{left}$ above $z$), $z$ becomes the child of $y$. If you call pullHeight(y) before pullHeight(z), $y$ computes its new height using $z$'s stale pre-rotation height, corrupting every ancestor's balance factor! Always recompute the demoted child $z$ first, then the promoted parent $y$.
  • Strict Equality Trap on Deletion Rebalancing (bf(u->left) >= 0): When bf(u) == +2 after a deletion in $u.\text{right}$, bf(u->left) can be $0$ (unlike insertion where it is strictly $+1$ or $-1$). When bf(u->left) == 0, a single rotateRight(u) suffices; treating 0 as an LR double rotation performs unnecessary work.
Check yourself Question 1 of 4
Concept Check Minimum Nodes in an AVL Tree of Height h recall

Which recurrence relation defines $N(h)$, the minimum number of nodes in an AVL tree of height $h$ (where a single node has height $0$ and an empty tree has height $-1$)?

⚡ Interactive Challenge · Staff Level

Algorithmic Design & Complexity Check

Analyze the invariant, asymptotic bound, and hardware behavior before selecting.

Why do concurrent in-memory indexes and kernel subsystems overwhelmingly prefer Red-Black trees over AVL trees under write-heavy workloads?

Key Takeaways

Tree rotations (rotateLeft / rotateRight) rearrange local parent-child pointers in O(1) time while strictly preserving the global In-Order sequence α < x < β < y < γ.
AVL Trees enforce |h(left) - h(right)| <= 1 at every node; extremal worst-case AVL trees (Fibonacci Trees) satisfy N_h = 1 + N_{h-1} + N_{h-2} = F_{h+3} - 1, bounding height to < 1.4404 log₂(N + 2).
Outer imbalances (LL, RR) are fixed by a single rotation; inner zig-zag imbalances (LR, RL) require a double rotation that lifts the inner grandchild to the subtree root.
AVL insertion requires at most 1 rebalancing event (≤ 2 rotations total), whereas AVL deletion can cascade O(log N) rotations up to the root.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue