G27
Self-checkedTier 2 16 min readpds.avl-trees

AVL trees and rotations

A binary search tree that repairs itself: the balance factor and the AVL condition, the four cases LL, RR, LR and RL and their single and double rotations, insertion (at most one repair) and deletion (repairs up to the root), the fewest nodes of an AVL tree of a given height (a Fibonacci recurrence) and the resulting bound of about 1.44 log n on the height, and the cost of building and using AVL trees.

Why it exists

A binary search tree is fast only if it is shallow. Insert the keys 1, 2, 3, ... in order and it becomes a chain: height n−1n - 1 and every operation takes O(n)O(n). An AVL tree (Adelson-Velsky and Landis) never lets that happen. It is a binary search tree that checks itself after every insertion and deletion and repairs any imbalance with a few pointer changes called rotations. The result is a height of O(log⁡n)O(\log n) in the worst case, so search, insert and delete are all O(log⁡n)O(\log n) in the worst case. It is the first self-balancing tree and the standard answer to "how can a search tree guarantee logarithmic time?".

Prerequisites

You need the binary search tree: the ordering rule, insertion and deletion (including the case of a node with two children and its in-order successor), and the fact that the shape depends on the insertion order. The size bound uses a recurrence solved like those in recurrences.

Precise definitions

Height. The height of a node is the number of edges on the longest downward path to a leaf. The height of an empty tree is −1-1, of a single node 00. (Some books say 1 for a single node and 0 for the empty tree; all heights then shift by one. A GATE question states its convention: read it.)

Balance factor of a node: bf=h(left)−h(right)bf = h(\text{left}) - h(\text{right}).

AVL tree. A binary search tree in which every node has bf∈{−1,0,+1}bf \in \{-1, 0, +1\}: the heights of its two subtrees differ by at most 1. (This is a condition on every node, not only on the root.)

Rotation. A local change of three pointers that keeps the in-order sequence of the keys unchanged and changes the heights:

  • Right rotation at yy (with left child xx): xx becomes the root of the subtree, yy becomes its right child, and the old right subtree of xx becomes the left subtree of yy.
  • Left rotation at xx (with right child yy): the mirror image.

The four cases. After an insertion (or deletion) let zz be the lowest node with ∣bf∣=2|bf| = 2.

CaseWhere the extra height isRepair
LLbf(z)=+2bf(z) = +2 and bf(left child)≥0bf(\text{left child}) \ge 0one right rotation at zz
RRbf(z)=−2bf(z) = -2 and bf(right child)≤0bf(\text{right child}) \le 0one left rotation at zz
LRbf(z)=+2bf(z) = +2 and bf(left child)=−1bf(\text{left child}) = -1left rotation at the left child, then right rotation at zz
RLbf(z)=−2bf(z) = -2 and bf(right child)=+1bf(\text{right child}) = +1right rotation at the right child, then left rotation at zz

After an insertion the child's balance factor is never 0 in this situation; after a deletion it can be 0, and then a single rotation is correct (the cases LL and RR with the child at 0).

How it works

Insertion

Insert like in an ordinary search tree, then walk back up to the root, recompute heights and balance factors, and repair the first node that is out of balance.

AVL insertion: 10, 20, 30, 40, 50, 25: start

Computed by a script, not typed
keyLeftRightHeightBalance
10
20
25
30
40
50

Step 0

Keys are inserted one after another. A row is a node: its left and right child (- = none), its height (a single node has height 0) and its balance factor (height of the left subtree minus the height of the right subtree). The table only fills as the nodes appear.

0 / 9
computed by script: the AVL tree of scripts/avl_lib.py is built and changed step by step. python3 scripts/gen-pds-avl-traces.py

Each row is a node (the table fills as the keys arrive). Inserting 30 makes 10 right-heavy by two: the case RR, a single left rotation, and 20 becomes the root. Inserting 50 does the same at 30. Inserting 25 puts a node in the left subtree of the right child of 20: the case RL, which one rotation cannot fix, so there is a double rotation (a right rotation at 40, which lifts 30, then a left rotation at 20). The result has the root 30, with 20 (children 10 and 25) on the left and 40 (right child 50) on the right. Watch the Left and Right columns change at every rotation.

Insertion needs at most one repair. After the repair the subtree has the height it had before the insertion, so no ancestor sees a change. The check over all insertion orders of up to 8 keys found one rebalancing step at most (a double rotation is one step with two rotations).

The four cases

The four rebalancing cases: start

Computed by a script, not typed
caseInsertion orderUnbalanced nodeHeavy sideRepairShape beforeShape after
LL
RR
LR
RL

Step 0

The smallest tree for each case. The name says where the new node went relative to the unbalanced node z: the first letter is the side of the heavy child, the second the side of the grandchild.

0 / 4
computed by script: the AVL tree of scripts/avl_lib.py is built and changed step by step. python3 scripts/gen-pds-avl-traces.py

In each case the middle key of the three becomes the root of the subtree, and the other two become its children; the pictures differ only in how the smaller subtrees are reattached. A double rotation is needed when the new node lies in the inner subtree (LR, RL) and a single one when it lies in the outer subtree (LL, RR).

Deletion

Delete like in an ordinary search tree (a node with two children is replaced by its in-order successor, which is then removed), then walk up from the place of the removal and repair every node that is out of balance. Unlike the insertion, the repair can shrink the subtree, so the parent may become unbalanced in turn:

AVL deletion: remove 11 from the smallest AVL tree of height 4: start

Computed by a script, not typed
keyLeftRightHeightBalance
1
2
3
4
5
6
7
8
9
10
11
12

Step 0

Deleting a node can shorten a subtree, which unbalances an ancestor; repairing that ancestor can shorten its subtree again, and so on up to the root. The table shows the node table after each stage.

0 / 4
computed by script: the AVL tree of scripts/avl_lib.py is built and changed step by step. python3 scripts/gen-pds-avl-traces.py

This is the smallest AVL tree of height 4 (12 nodes). Removing key 11 leaves 12 left-heavy by two; the repair shortens that subtree; now 8 is out of balance by two, and a second rotation is needed. In the worst case a deletion repairs at every level up to the root, so it may need O(log⁡n)O(\log n) rotations:

How many rotations does one operation need?: start

Computed by a script, not typed
operationNodes in the treeRebalancing stepsRotations
any insertion (all orders of up to 7 keys)
worst deletion, smallest tree of height 2
worst deletion, smallest tree of height 3
worst deletion, smallest tree of height 4
worst deletion, smallest tree of height 5
worst deletion, smallest tree of height 6
worst deletion, smallest tree of height 7
worst deletion, smallest tree of height 8

Step 0

An insertion needs at most one rebalancing step (one single or one double rotation): after it the subtree is as high as before the insertion, so nothing above changes. A deletion can shorten the subtree, so rebalancing can repeat on the way up to the root.

0 / 8
computed by script: the AVL tree of scripts/avl_lib.py is built and changed step by step. python3 scripts/gen-pds-avl-traces.py

(The table is a measurement of the smallest trees, not a proof of the worst case, but it shows the pattern: about half the height.)

How tall can an AVL tree be?

Let N(h)N(h) be the fewest nodes of an AVL tree of height hh. The sparsest such tree has a root, a subtree of height h−1h - 1 and a subtree of height h−2h - 2 (any shorter and the condition fails; any smaller subtree would shorten the whole tree):

N(h)=N(h−1)+N(h−2)+1,N(−1)=0, N(0)=1N(h) = N(h-1) + N(h-2) + 1, \qquad N(-1) = 0,\ N(0) = 1

This gives 1, 2, 4, 7, 12, 20, 33, 54, ... for h=0,1,2,…h = 0, 1, 2, \dots, which is the Fibonacci sequence minus one: N(h)=Fh+3−1N(h) = F_{h+3} - 1 (with F1=F2=1F_1 = F_2 = 1). The most nodes of height hh is 2h+1−12^{h+1} - 1 (a perfect tree).

Size and height of AVL trees: start

Computed by a script, not typed
height hFewest nodes N(h)Most nodesn for which h is the largest possible heightSmallest possible height for those n
height 0
height 1
height 2
height 3
height 4
height 5
height 6
height 7
height 8
height 9
height 10

Step 0

N(h) = N(h-1) + N(h-2) + 1 with N(0) = 1 and N(-1) = 0 is the fewest nodes of an AVL tree of height h: the root plus one subtree of height h-1 and one of height h-2. These are the Fibonacci numbers minus one.

0 / 11
computed by script: the AVL tree of scripts/avl_lib.py is built and changed step by step. python3 scripts/gen-pds-avl-traces.py

Read the table from the right: an AVL tree with nn nodes has height at most the largest hh with N(h)≤nN(h) \le n. The exercise "what is the largest height of an AVL tree with 7 nodes?" is answered by the row for N(3)=7N(3) = 7: 3. Because N(h)N(h) grows like φh\varphi^{h} with φ≈1.618\varphi \approx 1.618 (the golden ratio), h<1.44log⁡2(n+2)h < 1.44 \log_2 (n + 2) for every AVL tree, and h≥⌊log⁡2n⌋h \ge \lfloor \log_2 n \rfloor for every binary tree. So the height is Θ(log⁡n)\Theta(\log n), between log⁡2n\log_2 n and about 1.44log⁡2n1.44 \log_2 n.

Sorted input

Sorted input: plain search tree against AVL: start

Computed by a script, not typed
keysHeight, plain treeHeight, AVL treeSmallest possible heightRebalancing stepsRotations
n = 7
n = 15
n = 31
n = 63
n = 127
n = 255
n = 1023

Step 0

The worst input for a plain binary search tree: the keys arrive already sorted. The AVL tree repairs itself and stays shallow.

0 / 7
computed by script: the AVL tree of scripts/avl_lib.py is built and changed step by step. python3 scripts/gen-pds-avl-traces.py

The plain tree becomes a chain, the AVL tree has the smallest possible height for every size shown. Inserting 1,…,n1, \dots, n in order for n=2k−1n = 2^k - 1 costs n−kn - k rotations, all single, and ends in a perfect tree. The program below does the insertions with the exact rotation rules of this page.

#include <stdio.h>
#include <stdlib.h>
 
struct node {
    int key, height;
    struct node *left, *right;
};
 
static int rotations = 0;
 
static int height(const struct node *n) { return n ? n->height : -1; }
static int max(int a, int b) { return a > b ? a : b; }
static void update(struct node *n) { n->height = 1 + max(height(n->left), height(n->right)); }
 
static struct node *rotate_right(struct node *y) {
    struct node *x = y->left;
    y->left = x->right;
    x->right = y;
    update(y);
    update(x);
    rotations++;
    return x;
}
 
static struct node *rotate_left(struct node *x) {
    struct node *y = x->right;
    x->right = y->left;
    y->left = x;
    update(x);
    update(y);
    rotations++;
    return y;
}
 
static struct node *rebalance(struct node *n) {
    update(n);
    int balance = height(n->left) - height(n->right);
    if (balance > 1) {
        if (height(n->left->left) < height(n->left->right)) n->left = rotate_left(n->left);   /* case LR */
        return rotate_right(n);
    }
    if (balance < -1) {
        if (height(n->right->right) < height(n->right->left)) n->right = rotate_right(n->right);   /* case RL */
        return rotate_left(n);
    }
    return n;
}
 
static struct node *insert(struct node *n, int key) {
    if (n == NULL) {
        n = calloc(1, sizeof *n);
        if (n == NULL) exit(1);
        n->key = key;
        return n;
    }
    if (key < n->key) n->left = insert(n->left, key);
    else if (key > n->key) n->right = insert(n->right, key);
    else return n;
    return rebalance(n);
}
 
static void show(const struct node *n) {
    if (n == NULL) { printf("."); return; }
    if (n->left == NULL && n->right == NULL) { printf("%d", n->key); return; }
    printf("(");
    show(n->left);
    printf(" %d ", n->key);
    show(n->right);
    printf(")");
}
 
static void release(struct node *n) {
    if (n == NULL) return;
    release(n->left);
    release(n->right);
    free(n);
}
 
int main(void) {
    int keys[] = {10, 20, 30, 40, 50, 25};
    struct node *root = NULL;
    for (int i = 0; i < 6; i++) {
        root = insert(root, keys[i]);
        printf("insert %d: ", keys[i]);
        show(root);
        printf("   rotations so far: %d\n", rotations);
    }
    release(root);
    for (int n = 7; n <= 1023; n = 2 * n + 1) {
        rotations = 0;
        root = NULL;
        for (int k = 1; k <= n; k++) root = insert(root, k);
        printf("keys 1..%d in order: height %d, %d rotations\n", n, root->height, rotations);
        release(root);
    }
    return 0;
}
insert 10: 10   rotations so far: 0
insert 20: (. 10 20)   rotations so far: 0
insert 30: (10 20 30)   rotations so far: 1
insert 40: (10 20 (. 30 40))   rotations so far: 1
insert 50: (10 20 (30 40 50))   rotations so far: 2
insert 25: ((10 20 25) 30 (. 40 50))   rotations so far: 4
keys 1..7 in order: height 2, 4 rotations
keys 1..15 in order: height 3, 11 rotations
keys 1..31 in order: height 4, 26 rotations
keys 1..63 in order: height 5, 57 rotations
keys 1..127 in order: height 6, 120 rotations
keys 1..255 in order: height 7, 247 rotations
keys 1..511 in order: height 8, 502 rotations
keys 1..1023 in order: height 9, 1013 rotations

The rotation counts of the table are the counts of this program, and the shapes are the ones of the node table. (A double rotation counts as two rotations here.)

Cost

Search, insertion and deletion each follow one path from the root and do a constant amount of work per node (a deletion may repair at every level), so each takes Θ(log⁡n)\Theta(\log n) in the worst case. Building a tree by nn insertions takes O(nlog⁡n)O(n \log n). Inserting mm keys into a tree that already has nn nodes costs Θ(mlog⁡(n+m))\Theta(m \log (n + m)) in total in the worst case (every new node is a leaf, and every leaf of an AVL tree with ss nodes lies at a depth of at least about half its height, so each insertion walks Θ(log⁡s)\Theta(\log s) nodes). Sorting by inserting into an AVL tree and reading it in order takes Θ(nlog⁡n)\Theta(n \log n), a worst-case guarantee that a plain search tree does not give. The extra cost is one height (or balance factor) stored in every node and the code for the rotations.

Worked examples

EX 1Which case, which repair?

Easy

Insert 50, 40, 30 into an empty AVL tree. After 30, node 50 has balance +2+2 and its left child 40 has balance +1+1: the case LL. One right rotation at 50 gives the root 40 with children 30 and 50. Insert 50, 30, 40 instead: after 40, node 50 has balance +2+2 and its left child 30 has balance −1-1: the case LR. Rotate left at 30 (40 becomes the left child of 50 with 30 below it), then right at 50: root 40, children 30 and 50. Both orders end in the same tree, because the middle key must become the root.

EX 2Largest and smallest height

Medium

(a) What is the largest possible height of an AVL tree with 20 nodes? N(5)=20N(5) = 20, so height 5. With 19 nodes, N(4)=12≤19<20N(4) = 12 \le 19 < 20: height 4. (b) What is the fewest nodes in an AVL tree of height 6? N(6)=33N(6) = 33. (c) What is the smallest height of a binary tree with 20 nodes? A perfect tree of height hh has 2h+1−12^{h+1} - 1 nodes, and 24−1=15<20≤312^4 - 1 = 15 < 20 \le 31, so height 4. An AVL tree with 20 nodes has height 4 or 5. (d) If a book counts a single node as height 1, add one to each answer.

EX 3Insert and rebalance by hand

Medium

Build an AVL tree from 20, 10, 30, 5, 15, 25, 35, 3, 1. The first seven keys make a perfect tree with root 20. Insert 3: it goes below 5, to the left; heights are fine (node 10 has balance +1+1). Insert 1: it goes left of 3. Now node 5 has balance +2+2 (left subtree 3-1 of height 1, right subtree empty, height −1-1) and its left child 3 has balance +1+1: the case LL at 5. Right rotation: 3 becomes the root of that subtree with children 1 and 5. Node 10 is balanced again (+1+1): nothing higher up changes, as expected for an insertion. The tree, written (left key right), is (((1 3 5) 10 15) 20 (25 30 35))(((1\ 3\ 5)\ 10\ 15)\ 20\ (25\ 30\ 35)).

EX 4Typical questions

GATE level

(a) "How many rotations does it take to insert 1, 2, 3, ..., 15 in order?" The program's table: 11 single rotations (it is n−kn - k for n=2k−1n = 2^k - 1). (b) "The maximum height of an AVL tree with 7 nodes, with height of a single node 0?" N(3)=7N(3) = 7, so 3. (c) "Insert mm elements into an AVL tree with nn elements: worst-case time?" Θ(mlog⁡(n+m))\Theta(m \log (n + m)); with m=n2m = n^2 this is Θ(n2log⁡n)\Theta(n^2 \log n). (d) "Which statement is true: an insertion needs at most one (single or double) rotation, a deletion may need O(log⁡n)O(\log n)." Both are true. (e) "Is every AVL tree a balanced binary tree in the sense that all leaves are at the same level?" No, only the heights of the two subtrees of every node differ by at most 1. (f) "Is a node's balance factor enough to find the rotation?" Yes, the balance factors of zz and of its heavy child decide between LL, RR, LR and RL.

Cheat box

Cheat box

  • AVL: ∣h(left)−h(right)∣≤1|h(\text{left}) - h(\text{right})| \le 1 at every node. Balance factor =h(L)−h(R)= h(L) - h(R). Height of one node =0= 0, of nothing =−1= -1.
  • Cases: LL and RR need one rotation; LR and RL need two (the inner subtree is heavy).
  • Insertion: at most one repair (one single or one double rotation). Deletion: possibly one repair per level, O(log⁡n)O(\log n) rotations.
  • Fewest nodes: N(h)=N(h−1)+N(h−2)+1N(h) = N(h-1) + N(h-2) + 1: 1, 2, 4, 7, 12, 20, 33, 54, ... (=Fh+3−1= F_{h+3} - 1). Most nodes: 2h+1−12^{h+1} - 1.
  • Height of an AVL tree with nn nodes: at most the largest hh with N(h)≤nN(h) \le n, and about 1.44log⁡2n1.44 \log_2 n at worst. All operations Θ(log⁡n)\Theta(\log n) worst case.

Traps and GATE patterns

Connections

The AVL tree repairs the weakness of the binary search tree, the dependence of the height on the insertion order. The B-tree solves the same problem differently, by allowing many keys per node and splitting nodes, which suits disks. Unlike the binary heap, an AVL tree keeps all keys sorted, so it can answer range queries and find a successor in O(log⁡n)O(\log n).

PYQs

2 previous year questions from your book. I re-solved each one myself (1 by running or simulating, 1 by reasoning only); the book's key is shown next to my answer. All PYQs

GATE 2020 · MCQQ12 · page 68 of the book
What is the worst case time complexity of inserting
n2 elements into an AVL-tree with n elements
initially?
  • (a)Θ(n4)
  • (b)Θ(n2)
  • (c)Θ(n3)
  • (d)Θ(n2 log n)
GATE 2009Q37 · page 72 of the book
What is the maximum height of any AVL-tree with 7
nodes ? Assume that the height of a tree with a single
node is 0.
  • (a)2
  • (b)3
  • (c)4
  • (d)5

Self-check

Question 1

An AVL tree has root 40 with a left subtree of height 2 and a right subtree of height 0. Is it an AVL tree? What is the balance factor of 40?

Show answer

The balance factor is 2−0=+22 - 0 = +2, so it is not an AVL tree (the condition allows −1,0,+1-1, 0, +1).

Question 2

What is the smallest number of nodes in an AVL tree of height 5 (a single node has height 0)?

Show answer

N(5)=N(4)+N(3)+1=12+7+1=20N(5) = N(4) + N(3) + 1 = 12 + 7 + 1 = 20.

Question 3

Insert 3, 1, 2 into an empty AVL tree. Which case, and what is the result?

Show answer

After 2, node 3 has balance +2+2 and its left child 1 has balance −1-1: the case LR. Rotate left at 1 (2 becomes the left child of 3), then right at 3: root 2 with children 1 and 3.

Revision card

One-screen revision

  • AVL: every node has bf=h(L)−h(R)∈{−1,0,1}bf = h(L) - h(R) \in \{-1, 0, 1\}. Repair at the lowest unbalanced node zz.
  • LL, RR: one rotation; LR, RL: two rotations. Middle key becomes the subtree root.
  • Insertion: at most one repair. Deletion: up to O(log⁡n)O(\log n) repairs; child balance 0 means a single rotation.
  • Fewest nodes N(h)=N(h−1)+N(h−2)+1N(h) = N(h-1) + N(h-2) + 1: 1, 2, 4, 7, 12, 20, 33. Height ≤1.44log⁡2(n+2)\le 1.44 \log_2 (n + 2).
  • All operations Θ(log⁡n)\Theta(\log n) worst case; sorted input is no problem.

References

  • ·Cormen, Leiserson, Rivest, Stein, Introduction to Algorithms, 3rd ed., Problem 13-3 (AVL trees) and Chapter 12 (binary search trees)
  • ·Weiss, Data Structures and Algorithm Analysis in C, 2nd ed., Section 4.4 AVL Trees
  • ·Knuth, The Art of Computer Programming, Volume 3, Section 6.2.3 Balanced Trees

Verification

scripts/enum-checks/pds_avl_check.py: (1) a second implementation (immutable tuples, heights recomputed from the structure on demand, rotations written separately) builds the same tree as scripts/avl_lib.py for EVERY insertion order of up to 8 keys (46233 orders), and for every deletion of every key from each of the 35 different AVL trees that insertion orders of up to 7 keys produce (197 deletions); (2) every tree is a binary search tree, satisfies the AVL condition (checked with heights recomputed from the structure, not the stored ones) and has correct stored heights, after every insertion and every deletion; (3) an insertion needs at most one rebalancing step in all these cases, and the worst deletions of the smallest AVL trees of height 2 to 8 need floor(h/2) steps (measured, not a proof that this is the worst case over all trees); (4) the sizes of AVL trees are found by a dynamic program on the sets of possible sizes per height up to height 12 and by generating every AVL SHAPE up to height 4: the fewest nodes of height h is N(h) = N(h-1) + N(h-2) + 1, the most 2^(h+1) - 1, and every size between occurs; (5) the largest height of an AVL tree with n nodes (n up to 200) equals the maximum over all AVL trees of that size, and the largest height reached by insertion orders of up to 8 keys equals it; for n up to 10^6 it stays below 1.4405 log2(n + 2); (6) the C program printed on the page is compiled and run and its output equals the library's trees and rotation counts. tests/pds-avl-traces.test.ts recomputes the node tables of the insertion and the sizes in TypeScript. CONVENTIONS: the height of an empty tree is -1 and of a single node 0 (a book that counts a single node as height 1 gives every height one more, and every answer 'h' becomes 'h + 1'); the balance factor is height(left) - height(right); deleting a node with two children uses the in-order successor; after a deletion the child's balance factor 0 needs one single rotation; a double rotation counts as one rebalancing step and two rotations. Textbook chapter and section numbers are from memory and need verification. SECOND PASS (2026-11-02, done by the assistant, not a human check): every Self-check question was solved before the printed answer was read and all agreed; the definitions, worked examples, cheat box and traps were read claim by claim and the numbers re-derived by hand (long C programs were not re-read line by line: they are compiled and compared by npm run verify:c, and their printed outputs were checked against the prose). One imprecise phrase fixed (the double rotation after inserting 25 is a right rotation at 40 and a left rotation at 20). Status raised to self-checked; verified needs a textbook or human check.

last verified: never

My notes (saved in this browser)