AVL trees
AVL trees: the balance factor, the four rotation cases, rebalancing after insertion and deletion, and why the height stays logarithmic.
Learning goals
- Compute a balance factor and recognise the four imbalance cases
- Fix each case with one or two rotations
- Write AVL insertion and deletion with rebalancing on the way back up
- Derive the logarithmic height bound from the smallest AVL trees
Prerequisites
A plain BST becomes a list when keys arrive in order. An AVL tree repairs its own shape after every update, so searches stay at O(log n) whatever the input order.
Balance factor
The **balance factor** of a node is the height of its left subtree minus the height of its right subtree. An **AVL tree** is a BST in which every node has a balance factor of -1, 0 or +1. The invariant is local, one number per node, and a node only needs its two children's heights to check it, so each node stores its own height.
The animation prints the factor under every node. Insert 10, 20 and 30 in that order (the first preset): after 30 arrives the root 10 has factor -2, which breaks the invariant and triggers a rotation. With a leaf at height 0 and an empty subtree at -1, the height of a node is 1 + max(child heights).
1static class Node {2int key, height; // a leaf has height 03Node left, right;4Node(int key) { this.key = key; }5}6static int h(Node n) { return n == null ? -1 : n.height; }7static void update(Node n) { n.height = 1 + Math.max(h(n.left), h(n.right)); }8static int balance(Node n) { return h(n.left) - h(n.right); } // allowed values: -1, 0, +1
AVL tree: insertion and deletion with rotations
Interactive · edit the inputAn AVL tree keeps every node's balance factor (left height minus right height) within -1..+1 by rotating when an insertion or deletion breaks it. The factor is shown under each node.
1static class Node { int key, height; Node left, right; Node(int k) { key = k; } } // a leaf has height 02static int h(Node n) { return n == null ? -1 : n.height; }3static void update(Node n) { n.height = 1 + Math.max(h(n.left), h(n.right)); }4static int balance(Node n) { return h(n.left) - h(n.right); }5static Node rotateRight(Node y) { // the left child x becomes the subtree root6Node x = y.left;7y.left = x.right;8x.right = y;9update(y); update(x);10return x;11}12static Node rotateLeft(Node x) {13Node y = x.right;14x.right = y.left;15y.left = x;16update(x); update(y);17return y;18}19static Node rebalance(Node n) {20update(n);21int b = balance(n);22if (b > 1) { // left-heavy23if (balance(n.left) < 0) n.left = rotateLeft(n.left); // LR: first turn it into LL24return rotateRight(n);25}26if (b < -1) { // right-heavy27if (balance(n.right) > 0) n.right = rotateRight(n.right); // RL: first turn it into RR28return rotateLeft(n);29}30return n;31}32static Node insert(Node n, int key) {33if (n == null) return new Node(key);34if (key < n.key) n.left = insert(n.left, key);35else if (key > n.key) n.right = insert(n.right, key);36else return n; // duplicates are ignored37return rebalance(n); // repair this node on the way back up38}39static Node delete(Node n, int key) {40if (n == null) return null;41if (key < n.key) n.left = delete(n.left, key);42else if (key > n.key) n.right = delete(n.right, key);43else {44if (n.left == null) return n.right;45if (n.right == null) return n.left;46Node s = n.right;47while (s.left != null) s = s.left; // inorder successor48n.key = s.key;49n.right = delete(n.right, s.key);50}51return rebalance(n);52}
Live state
height
-1
Execution trace (dry run table)Show 34 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L32 | height=-1 | An AVL tree keeps every node's balance factor (left height minus right height) within -1..+1 by rotating when an insertion or deletion breaks it. The factor is shown under each node. |
| 2 | L33 | height=0 inserted=10 | Insert 10: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 3 | L35 | height=0 inserted=20 | 20 > 10: go right. |
| 4 | L33 | height=0 inserted=20 | Insert 20: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 5 | L22 | height=1 node=10 balance=-1 | Back at 10: heights updated, balance factor -1. Within ±1, so no rotation. |
| 6 | L35 | height=1 inserted=30 | 30 > 10: go right. |
| 7 | L35 | height=1 inserted=30 | 30 > 20: go right. |
| 8 | L33 | height=1 inserted=30 | Insert 30: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 9 | L22 | height=1 node=20 balance=-1 | Back at 20: heights updated, balance factor -1. Within ±1, so no rotation. |
| 10 | L26 | height=2 node=10 balance=-2 case=RR | Node 10 has balance factor -2: its right subtree is 2 taller. Its right child 20 has balance -1, so this is the right-right (RR) case. |
| 11 | L28 | height=1 case=RR | Rotate 10 left: 20 becomes the subtree root, 10 its left child. The subtree's height is back to 1. |
| 12 | L35 | height=1 inserted=40 | 40 > 20: go right. |
| 13 | L35 | height=1 inserted=40 | 40 > 30: go right. |
| 14 | L33 | height=1 inserted=40 | Insert 40: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 15 | L22 | height=1 node=30 balance=-1 | Back at 30: heights updated, balance factor -1. Within ±1, so no rotation. |
| 16 | L22 | height=2 node=20 balance=-1 | Back at 20: heights updated, balance factor -1. Within ±1, so no rotation. |
| 17 | L35 | height=2 inserted=50 | 50 > 20: go right. |
| 18 | L35 | height=2 inserted=50 | 50 > 30: go right. |
| 19 | L35 | height=2 inserted=50 | 50 > 40: go right. |
| 20 | L33 | height=2 inserted=50 | Insert 50: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 21 | L22 | height=2 node=40 balance=-1 | Back at 40: heights updated, balance factor -1. Within ±1, so no rotation. |
| 22 | L26 | height=2 node=30 balance=-2 case=RR | Node 30 has balance factor -2: its right subtree is 2 taller. Its right child 40 has balance -1, so this is the right-right (RR) case. |
| 23 | L28 | height=2 case=RR | Rotate 30 left: 40 becomes the subtree root, 30 its left child. The subtree's height is back to 1. |
| 24 | L22 | height=2 node=20 balance=-1 | Back at 20: heights updated, balance factor -1. Within ±1, so no rotation. |
| 25 | L35 | height=2 inserted=25 | 25 > 20: go right. |
| 26 | L34 | height=2 inserted=25 | 25 < 40: go left. |
| 27 | L34 | height=2 inserted=25 | 25 < 30: go left. |
| 28 | L33 | height=2 inserted=25 | Insert 25: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 29 | L22 | height=2 node=30 balance=+1 | Back at 30: heights updated, balance factor +1. Within ±1, so no rotation. |
| 30 | L22 | height=2 node=40 balance=+1 | Back at 40: heights updated, balance factor +1. Within ±1, so no rotation. |
| 31 | L26 | height=3 node=20 balance=-2 case=RL | Node 20 has balance factor -2: its right subtree is 2 taller. Its right child 40 has balance +1, so this is the right-left (RL) case. |
| 32 | L27 | height=3 case=RL | RL step 1: rotate 40 right, so 30 moves above it. Now the case is RR. |
| 33 | L28 | height=2 case=RL | Rotate 20 left: 30 becomes the subtree root, 20 its left child. The subtree's height is back to 2. |
| 34 | L50 | keys=6 height=2 minimum height=2 | 4 rotations in total. Height 2 for 6 keys; a perfectly balanced tree would have 2. |
LL/RR/LR/RL rotations
When a node's factor reaches +2 or -2, a **rotation** restores it without breaking the BST order: it lifts one child above its parent and moves one grandchild across. Name the case after the path from the unbalanced node z toward the new weight. **LL**: left child, left-heavy: rotate z right. **RR**: mirror image: rotate z left. **LR**: z's left child is right-heavy: rotate that child left (making it LL), then rotate z right. **RL**: the mirror image.
The first four presets show one case each: inserting 10, 20, 30 is RR (one left rotation), 30, 20, 10 is LL, 30, 10, 20 is LR and 10, 30, 20 is RL; each single rotation counts 1 and each double rotation 2. After any of the four, the subtree has its old height again, which is why a single repair suffices after an insertion.
1static Node rotateRight(Node y) { // the left child x becomes the subtree root2Node x = y.left;3y.left = x.right; // x's right subtree switches sides4x.right = y;5update(y); update(x); // lower node first: its height feeds the upper one6return x;7}8static Node rotateLeft(Node x) {9Node y = x.right;10x.right = y.left;11y.left = x;12update(x); update(y);13return y;14}15static Node rebalance(Node n) {16update(n);17int b = balance(n);18if (b > 1) { // left-heavy19if (balance(n.left) < 0) n.left = rotateLeft(n.left); // LR: first turn it into LL20return rotateRight(n);21}22if (b < -1) { // right-heavy23if (balance(n.right) > 0) n.right = rotateRight(n.right); // RL: first turn it into RR24return rotateLeft(n);25}26return n;27}
AVL tree: insertion and deletion with rotations
Interactive · edit the inputAn AVL tree keeps every node's balance factor (left height minus right height) within -1..+1 by rotating when an insertion or deletion breaks it. The factor is shown under each node.
1static class Node { int key, height; Node left, right; Node(int k) { key = k; } } // a leaf has height 02static int h(Node n) { return n == null ? -1 : n.height; }3static void update(Node n) { n.height = 1 + Math.max(h(n.left), h(n.right)); }4static int balance(Node n) { return h(n.left) - h(n.right); }5static Node rotateRight(Node y) { // the left child x becomes the subtree root6Node x = y.left;7y.left = x.right;8x.right = y;9update(y); update(x);10return x;11}12static Node rotateLeft(Node x) {13Node y = x.right;14x.right = y.left;15y.left = x;16update(x); update(y);17return y;18}19static Node rebalance(Node n) {20update(n);21int b = balance(n);22if (b > 1) { // left-heavy23if (balance(n.left) < 0) n.left = rotateLeft(n.left); // LR: first turn it into LL24return rotateRight(n);25}26if (b < -1) { // right-heavy27if (balance(n.right) > 0) n.right = rotateRight(n.right); // RL: first turn it into RR28return rotateLeft(n);29}30return n;31}32static Node insert(Node n, int key) {33if (n == null) return new Node(key);34if (key < n.key) n.left = insert(n.left, key);35else if (key > n.key) n.right = insert(n.right, key);36else return n; // duplicates are ignored37return rebalance(n); // repair this node on the way back up38}39static Node delete(Node n, int key) {40if (n == null) return null;41if (key < n.key) n.left = delete(n.left, key);42else if (key > n.key) n.right = delete(n.right, key);43else {44if (n.left == null) return n.right;45if (n.right == null) return n.left;46Node s = n.right;47while (s.left != null) s = s.left; // inorder successor48n.key = s.key;49n.right = delete(n.right, s.key);50}51return rebalance(n);52}
Live state
height
-1
Execution trace (dry run table)Show 34 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L32 | height=-1 | An AVL tree keeps every node's balance factor (left height minus right height) within -1..+1 by rotating when an insertion or deletion breaks it. The factor is shown under each node. |
| 2 | L33 | height=0 inserted=10 | Insert 10: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 3 | L35 | height=0 inserted=20 | 20 > 10: go right. |
| 4 | L33 | height=0 inserted=20 | Insert 20: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 5 | L22 | height=1 node=10 balance=-1 | Back at 10: heights updated, balance factor -1. Within ±1, so no rotation. |
| 6 | L35 | height=1 inserted=30 | 30 > 10: go right. |
| 7 | L35 | height=1 inserted=30 | 30 > 20: go right. |
| 8 | L33 | height=1 inserted=30 | Insert 30: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 9 | L22 | height=1 node=20 balance=-1 | Back at 20: heights updated, balance factor -1. Within ±1, so no rotation. |
| 10 | L26 | height=2 node=10 balance=-2 case=RR | Node 10 has balance factor -2: its right subtree is 2 taller. Its right child 20 has balance -1, so this is the right-right (RR) case. |
| 11 | L28 | height=1 case=RR | Rotate 10 left: 20 becomes the subtree root, 10 its left child. The subtree's height is back to 1. |
| 12 | L35 | height=1 inserted=40 | 40 > 20: go right. |
| 13 | L35 | height=1 inserted=40 | 40 > 30: go right. |
| 14 | L33 | height=1 inserted=40 | Insert 40: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 15 | L22 | height=1 node=30 balance=-1 | Back at 30: heights updated, balance factor -1. Within ±1, so no rotation. |
| 16 | L22 | height=2 node=20 balance=-1 | Back at 20: heights updated, balance factor -1. Within ±1, so no rotation. |
| 17 | L35 | height=2 inserted=50 | 50 > 20: go right. |
| 18 | L35 | height=2 inserted=50 | 50 > 30: go right. |
| 19 | L35 | height=2 inserted=50 | 50 > 40: go right. |
| 20 | L33 | height=2 inserted=50 | Insert 50: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 21 | L22 | height=2 node=40 balance=-1 | Back at 40: heights updated, balance factor -1. Within ±1, so no rotation. |
| 22 | L26 | height=2 node=30 balance=-2 case=RR | Node 30 has balance factor -2: its right subtree is 2 taller. Its right child 40 has balance -1, so this is the right-right (RR) case. |
| 23 | L28 | height=2 case=RR | Rotate 30 left: 40 becomes the subtree root, 30 its left child. The subtree's height is back to 1. |
| 24 | L22 | height=2 node=20 balance=-1 | Back at 20: heights updated, balance factor -1. Within ±1, so no rotation. |
| 25 | L35 | height=2 inserted=25 | 25 > 20: go right. |
| 26 | L34 | height=2 inserted=25 | 25 < 40: go left. |
| 27 | L34 | height=2 inserted=25 | 25 < 30: go left. |
| 28 | L33 | height=2 inserted=25 | Insert 25: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 29 | L22 | height=2 node=30 balance=+1 | Back at 30: heights updated, balance factor +1. Within ±1, so no rotation. |
| 30 | L22 | height=2 node=40 balance=+1 | Back at 40: heights updated, balance factor +1. Within ±1, so no rotation. |
| 31 | L26 | height=3 node=20 balance=-2 case=RL | Node 20 has balance factor -2: its right subtree is 2 taller. Its right child 40 has balance +1, so this is the right-left (RL) case. |
| 32 | L27 | height=3 case=RL | RL step 1: rotate 40 right, so 30 moves above it. Now the case is RR. |
| 33 | L28 | height=2 case=RL | Rotate 20 left: 30 becomes the subtree root, 20 its left child. The subtree's height is back to 2. |
| 34 | L50 | keys=6 height=2 minimum height=2 | 4 rotations in total. Height 2 for 6 keys; a perfectly balanced tree would have 2. |
Insertion rebalancing
Insert as in a plain BST, then **rebalance on the way back up**: each recursive call returns the (possibly new) root of its subtree, and the parent reattaches it. The first ancestor whose factor becomes ±2 is repaired by one single or double rotation, and after that repair every ancestor above is balanced again with the same heights as before the insertion. So an insertion needs **at most one rebalancing step** (one single or one double rotation); in a test with random insertions into trees of up to 200 keys, no insertion ever needed more.
Run the “A mix of all four cases” preset (10, 20, 30, 40, 50, 25): the tree ends at height 2 for 6 keys after 4 rotations. The sorted-input preset (10 to 70) ends as a perfect tree of height 2 with 4 rotations, while a plain BST would be a chain of height 6.
1static Node insert(Node n, int key) {2if (n == null) return new Node(key);3if (key < n.key) n.left = insert(n.left, key);4else if (key > n.key) n.right = insert(n.right, key);5else return n; // duplicates are ignored6return rebalance(n); // repair this node on the way back up7}
Best
O(log n)
Average
O(log n)
Worst
O(log n)
Space
O(log n) recursion
AVL tree: insertion and deletion with rotations
Interactive · edit the inputAn AVL tree keeps every node's balance factor (left height minus right height) within -1..+1 by rotating when an insertion or deletion breaks it. The factor is shown under each node.
1static class Node { int key, height; Node left, right; Node(int k) { key = k; } } // a leaf has height 02static int h(Node n) { return n == null ? -1 : n.height; }3static void update(Node n) { n.height = 1 + Math.max(h(n.left), h(n.right)); }4static int balance(Node n) { return h(n.left) - h(n.right); }5static Node rotateRight(Node y) { // the left child x becomes the subtree root6Node x = y.left;7y.left = x.right;8x.right = y;9update(y); update(x);10return x;11}12static Node rotateLeft(Node x) {13Node y = x.right;14x.right = y.left;15y.left = x;16update(x); update(y);17return y;18}19static Node rebalance(Node n) {20update(n);21int b = balance(n);22if (b > 1) { // left-heavy23if (balance(n.left) < 0) n.left = rotateLeft(n.left); // LR: first turn it into LL24return rotateRight(n);25}26if (b < -1) { // right-heavy27if (balance(n.right) > 0) n.right = rotateRight(n.right); // RL: first turn it into RR28return rotateLeft(n);29}30return n;31}32static Node insert(Node n, int key) {33if (n == null) return new Node(key);34if (key < n.key) n.left = insert(n.left, key);35else if (key > n.key) n.right = insert(n.right, key);36else return n; // duplicates are ignored37return rebalance(n); // repair this node on the way back up38}39static Node delete(Node n, int key) {40if (n == null) return null;41if (key < n.key) n.left = delete(n.left, key);42else if (key > n.key) n.right = delete(n.right, key);43else {44if (n.left == null) return n.right;45if (n.right == null) return n.left;46Node s = n.right;47while (s.left != null) s = s.left; // inorder successor48n.key = s.key;49n.right = delete(n.right, s.key);50}51return rebalance(n);52}
Live state
height
-1
Execution trace (dry run table)Show 34 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L32 | height=-1 | An AVL tree keeps every node's balance factor (left height minus right height) within -1..+1 by rotating when an insertion or deletion breaks it. The factor is shown under each node. |
| 2 | L33 | height=0 inserted=10 | Insert 10: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 3 | L35 | height=0 inserted=20 | 20 > 10: go right. |
| 4 | L33 | height=0 inserted=20 | Insert 20: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 5 | L22 | height=1 node=10 balance=-1 | Back at 10: heights updated, balance factor -1. Within ±1, so no rotation. |
| 6 | L35 | height=1 inserted=30 | 30 > 10: go right. |
| 7 | L35 | height=1 inserted=30 | 30 > 20: go right. |
| 8 | L33 | height=1 inserted=30 | Insert 30: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 9 | L22 | height=1 node=20 balance=-1 | Back at 20: heights updated, balance factor -1. Within ±1, so no rotation. |
| 10 | L26 | height=2 node=10 balance=-2 case=RR | Node 10 has balance factor -2: its right subtree is 2 taller. Its right child 20 has balance -1, so this is the right-right (RR) case. |
| 11 | L28 | height=1 case=RR | Rotate 10 left: 20 becomes the subtree root, 10 its left child. The subtree's height is back to 1. |
| 12 | L35 | height=1 inserted=40 | 40 > 20: go right. |
| 13 | L35 | height=1 inserted=40 | 40 > 30: go right. |
| 14 | L33 | height=1 inserted=40 | Insert 40: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 15 | L22 | height=1 node=30 balance=-1 | Back at 30: heights updated, balance factor -1. Within ±1, so no rotation. |
| 16 | L22 | height=2 node=20 balance=-1 | Back at 20: heights updated, balance factor -1. Within ±1, so no rotation. |
| 17 | L35 | height=2 inserted=50 | 50 > 20: go right. |
| 18 | L35 | height=2 inserted=50 | 50 > 30: go right. |
| 19 | L35 | height=2 inserted=50 | 50 > 40: go right. |
| 20 | L33 | height=2 inserted=50 | Insert 50: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 21 | L22 | height=2 node=40 balance=-1 | Back at 40: heights updated, balance factor -1. Within ±1, so no rotation. |
| 22 | L26 | height=2 node=30 balance=-2 case=RR | Node 30 has balance factor -2: its right subtree is 2 taller. Its right child 40 has balance -1, so this is the right-right (RR) case. |
| 23 | L28 | height=2 case=RR | Rotate 30 left: 40 becomes the subtree root, 30 its left child. The subtree's height is back to 1. |
| 24 | L22 | height=2 node=20 balance=-1 | Back at 20: heights updated, balance factor -1. Within ±1, so no rotation. |
| 25 | L35 | height=2 inserted=25 | 25 > 20: go right. |
| 26 | L34 | height=2 inserted=25 | 25 < 40: go left. |
| 27 | L34 | height=2 inserted=25 | 25 < 30: go left. |
| 28 | L33 | height=2 inserted=25 | Insert 25: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 29 | L22 | height=2 node=30 balance=+1 | Back at 30: heights updated, balance factor +1. Within ±1, so no rotation. |
| 30 | L22 | height=2 node=40 balance=+1 | Back at 40: heights updated, balance factor +1. Within ±1, so no rotation. |
| 31 | L26 | height=3 node=20 balance=-2 case=RL | Node 20 has balance factor -2: its right subtree is 2 taller. Its right child 40 has balance +1, so this is the right-left (RL) case. |
| 32 | L27 | height=3 case=RL | RL step 1: rotate 40 right, so 30 moves above it. Now the case is RR. |
| 33 | L28 | height=2 case=RL | Rotate 20 left: 30 becomes the subtree root, 20 its left child. The subtree's height is back to 2. |
| 34 | L50 | keys=6 height=2 minimum height=2 | 4 rotations in total. Height 2 for 6 keys; a perfectly balanced tree would have 2. |
Deletion rebalancing
Deletion is the same BST deletion (leaf, one child, or the successor trick) followed by the same rebalance on every ancestor. Unlike insertion, a rotation may **not** restore the subtree's old height, so the repair can cascade upward: more than one ancestor may need rotating. A deletion costs O(log n) because there is at most one rebalancing step per level. In a random test with trees of up to 200 keys, a single deletion needed as many as 4 rebalancing steps.
One more difference: after deleting, the heavy child may have a balance factor of 0, which does not happen after an insertion. In that case a single rotation is correct (the code treats it as LL or RR), so the double-rotation test checks for a strictly opposite sign. The “Delete causes a rotation” preset builds 20, 10, 30, 5 and deletes 30: the root 20 becomes left-heavy by 2 and one right rotation repairs it.
1static Node delete(Node n, int key) {2if (n == null) return null;3if (key < n.key) n.left = delete(n.left, key);4else if (key > n.key) n.right = delete(n.right, key);5else {6if (n.left == null) return n.right;7if (n.right == null) return n.left;8Node s = n.right;9while (s.left != null) s = s.left; // inorder successor10n.key = s.key;11n.right = delete(n.right, s.key);12}13return rebalance(n); // every ancestor is checked, not just the first14}
Best
O(log n)
Average
O(log n)
Worst
O(log n)
Space
O(log n) recursion
AVL tree: insertion and deletion with rotations
Interactive · edit the inputAn AVL tree keeps every node's balance factor (left height minus right height) within -1..+1 by rotating when an insertion or deletion breaks it. The factor is shown under each node.
1static class Node { int key, height; Node left, right; Node(int k) { key = k; } } // a leaf has height 02static int h(Node n) { return n == null ? -1 : n.height; }3static void update(Node n) { n.height = 1 + Math.max(h(n.left), h(n.right)); }4static int balance(Node n) { return h(n.left) - h(n.right); }5static Node rotateRight(Node y) { // the left child x becomes the subtree root6Node x = y.left;7y.left = x.right;8x.right = y;9update(y); update(x);10return x;11}12static Node rotateLeft(Node x) {13Node y = x.right;14x.right = y.left;15y.left = x;16update(x); update(y);17return y;18}19static Node rebalance(Node n) {20update(n);21int b = balance(n);22if (b > 1) { // left-heavy23if (balance(n.left) < 0) n.left = rotateLeft(n.left); // LR: first turn it into LL24return rotateRight(n);25}26if (b < -1) { // right-heavy27if (balance(n.right) > 0) n.right = rotateRight(n.right); // RL: first turn it into RR28return rotateLeft(n);29}30return n;31}32static Node insert(Node n, int key) {33if (n == null) return new Node(key);34if (key < n.key) n.left = insert(n.left, key);35else if (key > n.key) n.right = insert(n.right, key);36else return n; // duplicates are ignored37return rebalance(n); // repair this node on the way back up38}39static Node delete(Node n, int key) {40if (n == null) return null;41if (key < n.key) n.left = delete(n.left, key);42else if (key > n.key) n.right = delete(n.right, key);43else {44if (n.left == null) return n.right;45if (n.right == null) return n.left;46Node s = n.right;47while (s.left != null) s = s.left; // inorder successor48n.key = s.key;49n.right = delete(n.right, s.key);50}51return rebalance(n);52}
Live state
height
-1
Execution trace (dry run table)Show 34 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L32 | height=-1 | An AVL tree keeps every node's balance factor (left height minus right height) within -1..+1 by rotating when an insertion or deletion breaks it. The factor is shown under each node. |
| 2 | L33 | height=0 inserted=10 | Insert 10: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 3 | L35 | height=0 inserted=20 | 20 > 10: go right. |
| 4 | L33 | height=0 inserted=20 | Insert 20: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 5 | L22 | height=1 node=10 balance=-1 | Back at 10: heights updated, balance factor -1. Within ±1, so no rotation. |
| 6 | L35 | height=1 inserted=30 | 30 > 10: go right. |
| 7 | L35 | height=1 inserted=30 | 30 > 20: go right. |
| 8 | L33 | height=1 inserted=30 | Insert 30: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 9 | L22 | height=1 node=20 balance=-1 | Back at 20: heights updated, balance factor -1. Within ±1, so no rotation. |
| 10 | L26 | height=2 node=10 balance=-2 case=RR | Node 10 has balance factor -2: its right subtree is 2 taller. Its right child 20 has balance -1, so this is the right-right (RR) case. |
| 11 | L28 | height=1 case=RR | Rotate 10 left: 20 becomes the subtree root, 10 its left child. The subtree's height is back to 1. |
| 12 | L35 | height=1 inserted=40 | 40 > 20: go right. |
| 13 | L35 | height=1 inserted=40 | 40 > 30: go right. |
| 14 | L33 | height=1 inserted=40 | Insert 40: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 15 | L22 | height=1 node=30 balance=-1 | Back at 30: heights updated, balance factor -1. Within ±1, so no rotation. |
| 16 | L22 | height=2 node=20 balance=-1 | Back at 20: heights updated, balance factor -1. Within ±1, so no rotation. |
| 17 | L35 | height=2 inserted=50 | 50 > 20: go right. |
| 18 | L35 | height=2 inserted=50 | 50 > 30: go right. |
| 19 | L35 | height=2 inserted=50 | 50 > 40: go right. |
| 20 | L33 | height=2 inserted=50 | Insert 50: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 21 | L22 | height=2 node=40 balance=-1 | Back at 40: heights updated, balance factor -1. Within ±1, so no rotation. |
| 22 | L26 | height=2 node=30 balance=-2 case=RR | Node 30 has balance factor -2: its right subtree is 2 taller. Its right child 40 has balance -1, so this is the right-right (RR) case. |
| 23 | L28 | height=2 case=RR | Rotate 30 left: 40 becomes the subtree root, 30 its left child. The subtree's height is back to 1. |
| 24 | L22 | height=2 node=20 balance=-1 | Back at 20: heights updated, balance factor -1. Within ±1, so no rotation. |
| 25 | L35 | height=2 inserted=25 | 25 > 20: go right. |
| 26 | L34 | height=2 inserted=25 | 25 < 40: go left. |
| 27 | L34 | height=2 inserted=25 | 25 < 30: go left. |
| 28 | L33 | height=2 inserted=25 | Insert 25: an empty spot, so it becomes a new leaf (balance factor 0). Now walk back up and repair. |
| 29 | L22 | height=2 node=30 balance=+1 | Back at 30: heights updated, balance factor +1. Within ±1, so no rotation. |
| 30 | L22 | height=2 node=40 balance=+1 | Back at 40: heights updated, balance factor +1. Within ±1, so no rotation. |
| 31 | L26 | height=3 node=20 balance=-2 case=RL | Node 20 has balance factor -2: its right subtree is 2 taller. Its right child 40 has balance +1, so this is the right-left (RL) case. |
| 32 | L27 | height=3 case=RL | RL step 1: rotate 40 right, so 30 moves above it. Now the case is RR. |
| 33 | L28 | height=2 case=RL | Rotate 20 left: 30 becomes the subtree root, 20 its left child. The subtree's height is back to 2. |
| 34 | L50 | keys=6 height=2 minimum height=2 | 4 rotations in total. Height 2 for 6 keys; a perfectly balanced tree would have 2. |
Height bound
Why is an AVL tree's height logarithmic? Ask for the **fewest nodes** N(h) in an AVL tree of height h. The sparsest such tree has a root, one subtree of height h - 1 (as sparse as possible) and the other of height h - 2, the smallest allowed: N(h) = N(h - 1) + N(h - 2) + 1, with N(0) = 1 and N(1) = 2. These are the **Fibonacci trees**, and N(h) grows exponentially, about 1.618^h.
The first values are 1, 2, 4, 7, 12, 20, 33, 54, 88, 143, 232 for heights 0 to 10. Inverting the relation gives h < 1.4405 · log2(n + 2) - 1.328, so an AVL tree is never more than about 44% taller than a perfectly balanced tree, and searches take at most that many comparisons.
| Height h | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Fewest nodes N(h) | 1 | 2 | 4 | 7 | 12 | 20 | 33 | 54 | 88 | 143 | 232 |
1static long minNodes(int height) { // fewest nodes in an AVL tree of this height2long prev = 1, cur = 2; // N(0), N(1)3if (height == 0) return 1;4for (int i = 2; i <= height; i++) { long next = cur + prev + 1; prev = cur; cur = next; }5return cur;6}
Practice ladder
Now try it yourself
Easy
- Balanced Binary Tree
- Convert Sorted Array to Binary Search Tree
Medium
- Balance a Binary Search Tree
- Count Complete Tree Nodes
Advanced
- Count of Smaller Numbers After Self (AVL)
- Implement an AVL tree with order statistics
Key takeaways
- An AVL tree keeps every balance factor (left height minus right height) in -1, 0, +1.
- LL and RR need one rotation; LR and RL need two, the first turning the case into LL or RR.
- Insertion needs at most one rebalancing step; deletion may repair several ancestors.
- The smallest AVL trees are Fibonacci trees, so the height is below 1.44 log2(n + 2).