7.16 AVL Trees

AVL trees were the first self-balancing binary search trees.

7.16 AVL Trees

AVL trees were the first self-balancing binary search trees. Introduced in 1962 by entity["people","Georgy Adelson-Velsky","AVL tree co-inventor"] and entity["people","Evgenii Landis","AVL tree co-inventor"], they established many of the balancing techniques that later tree structures would adopt.

The defining characteristic of an AVL tree is strict balance. Every node tracks the relative heights of its left and right subtrees. Whenever an insertion or deletion causes that difference to become too large, rotations restore balance immediately.

This strictness produces very shallow trees and excellent search performance. The trade-off is additional bookkeeping during updates.

Compared with red-black trees, AVL trees usually perform more rotations but often achieve slightly faster lookups because their height is smaller.

Problem

Consider inserting values into a binary search tree.

10
20
30
40
50

A naïve BST becomes:

10
  \\n   20
     \\n      30
        \\n         40
           \\n            50

Height:

5

Searching for:

50

requires traversing every node.

To maintain logarithmic performance, we need to detect imbalance and repair it before the tree becomes excessively tall.

Understanding AVL Balance

An AVL tree associates a height with every node.

Example:

        40
       /  \\n     20    60
    / \    / \\n  10 30 50 70

Node heights:

10 = 1
30 = 1
50 = 1
70 = 1

20 = 2
60 = 2

40 = 3

The AVL rule states:

|height(left) - height(right)| ≤ 1

for every node.

This difference is called the balance factor.

Balance Factor

The balance factor of a node is:

height(left) - height(right)

Examples:

Balance = 0

Perfectly balanced.

Balance = 1

Left subtree is one level taller.

Balance = -1

Right subtree is one level taller.

These values are valid.

However:

Balance = 2

or:

Balance = -2

indicates imbalance.

The tree must be repaired.

Example of Imbalance

Insert:

10
20
30

Result:

10
  \\n   20
     \\n      30

Heights:

30 = 1
20 = 2
10 = 3

Balance factors:

30 = 0
20 = -1
10 = -2

Node 10 violates the AVL rule.

A rotation is required.

AVL Node Structure

A typical node stores:

type Node struct {
	Key    int
	Height int

	Left  *Node
	Right *Node
}

The stored height allows balance factors to be computed efficiently.

Without this metadata, height calculations would require repeated subtree traversals.

Height Calculation

Helper function:

func Height(n *Node) int {
	if n == nil {
		return 0
	}

	return n.Height
}

Balance factor:

func Balance(n *Node) int {
	if n == nil {
		return 0
	}

	return Height(n.Left) -
		Height(n.Right)
}

These small functions appear throughout AVL implementations.

Updating Heights

Whenever a subtree changes:

func UpdateHeight(n *Node) {
	left := Height(n.Left)
	right := Height(n.Right)

	if left > right {
		n.Height = left + 1
	} else {
		n.Height = right + 1
	}
}

Every rotation must update heights before returning.

Incorrect height updates are one of the most common AVL bugs.

The Four AVL Cases

Every AVL imbalance falls into one of four patterns.

Left-Left (LL)

      z
     /
    y
   /
  x

Repair:

Right rotation

Result:

      y
     / \\n    x   z

LL Rotation Example

Before:

    30
   /
 20
 /
10

After:

    20
   /  \\n 10    30

A single right rotation restores balance.

Right-Right (RR)

Mirror image of LL.

  z
   \\n    y
     \\n      x

Repair:

Left rotation

Example:

10
  \\n   20
     \\n      30

After:

    20
   /  \\n 10    30

Left-Right (LR)

Structure:

      z
     /
    x
     \\n      y

A single rotation is insufficient.

Step 1:

Rotate left at x

Step 2:

Rotate right at z

Result:

      y
     / \\n    x   z

LR Example

Before:

      30
     /
   10
     \\n      20

Rotate left:

      30
     /
    20
   /
 10

Rotate right:

      20
     /  \\n   10    30

Balance restored.

Right-Left (RL)

Mirror image of LR.

    z
     \\n      x
     /
    y

Step 1:

Rotate right at x

Step 2:

Rotate left at z

Result:

      y
     / \\n    z   x

These four cases cover every AVL imbalance.

Insertion

Insertion begins as an ordinary BST insertion.

func Insert(
	root *Node,
	key int,
) *Node

After insertion:

  1. Update heights.
  2. Compute balance factor.
  3. Apply the appropriate rotation if necessary.

The search path from the insertion point back to the root is the only region that can become imbalanced.

This locality keeps insertion efficient.

Simplified Insertion Flow

Insert node

Update height

Compute balance

If LL:
    rotate right

If RR:
    rotate left

If LR:
    rotate left
    rotate right

If RL:
    rotate right
    rotate left

Most AVL insertion code follows exactly this structure.

Example Walkthrough

Insert:

30
20
10

Tree:

      30
     /
   20
  /
10

Balance:

30 = 2

Violation detected.

Apply LL repair.

      20
     /  \\n   10    30

The height drops immediately.

Deletion

Deletion is more complicated than insertion.

Removing a node can decrease subtree heights, causing imbalance to propagate upward.

Unlike insertion, multiple rotations may be required while walking back toward the root.

The same four cases still apply:

LL
RR
LR
RL

The difference is that deletion may trigger balancing at several levels.

Why AVL Trees Search Quickly

Because AVL trees enforce strict balance, their height remains very close to optimal.

For:

1,000,000 nodes

Typical AVL height is around:

20

to

21

levels.

This is often slightly better than red-black trees.

Search operations therefore perform fewer comparisons on average.

AVL Trees Versus Red-Black Trees

AVL trees maintain stricter balance.

Red-black trees permit more variation.

Consequences:

Feature AVL Red-Black
Search speed Faster Slightly slower
Insert speed Slightly slower Faster
Delete speed Slightly slower Faster
Height Smaller Larger
Rotations More frequent Less frequent

The difference is usually modest, but it influences system design.

AVL Trees Versus Treaps

Treaps rely on random priorities.

AVL trees rely on deterministic height constraints.

Feature AVL Treap
Balance Deterministic Randomized
Height guarantee Worst-case O(log n) Expected O(log n)
Rotations Structured Priority-driven
Implementation complexity Moderate Low
Search performance Excellent Very good

AVL trees provide stronger guarantees.

Treaps provide simpler code.

Real-World Usage

AVL trees are particularly attractive when:

  • Searches dominate updates
  • Predictable lookup latency matters
  • Data remains mostly static
  • Ordered access is required

Examples include:

  • Read-heavy indexes
  • Routing tables
  • Symbol tables
  • Configuration stores
  • Memory-resident catalogs

In highly write-intensive systems, red-black trees are often preferred.

Complexity Analysis

AVL height remains:

O(log n)

Therefore:

Operation Complexity
Search O(log n)
Insert O(log n)
Delete O(log n)
Minimum O(log n)
Maximum O(log n)
Lower bound O(log n)
Upper bound O(log n)

Space complexity:

O(n)

The height field adds only constant overhead per node.

Common Mistakes

A common error is updating heights before rotations but not after rotations. Every node participating in a rotation must have its height recomputed.

Another mistake is confusing balance factors. The convention:

height(left) - height(right)

must be applied consistently.

A third mistake is performing the wrong rotation sequence. LR and RL cases require two rotations, not one.

Finally, many implementations recalculate subtree heights recursively. Storing heights directly in each node is essential for maintaining logarithmic update costs.

Takeaway

AVL trees maintain logarithmic height through strict balance-factor constraints. By tracking subtree heights and applying one of four rotation patterns whenever imbalance appears, they keep search paths short and predictable. Their stronger balancing rules generally produce faster lookups than red-black trees at the cost of additional maintenance during updates. AVL trees illustrate a central theme in data structure design: paying a small cost during modification can significantly improve future queries.