11.3 AVL trees
The AVL tree is a BST with the following additional property:
For every node, the heights of its subtrees differ by at most 1.
As long as the tree maintains this property, if the tree contains nodes, then it has a depth of at most . As a result, search for any node will cost , and if the updates can be done in time proportional to the depth of the node inserted or deleted, then updates will also cost , even in the worst case.
The key to making the AVL tree work is to alter the insert and delete routines so as to maintain the balance property. Of course, to be practical, we must be able to implement the revised update routines in time. To maintain the balance property, we are going to use what are called rotations.
11.3.1 Rotations
Rotation is an operation that takes a node in the tree and moves it one level higher. Figure 11.3 illustrates rotation. Here, and are nodes, while , and represent subtrees.

In figure (a), node is the left child of the root. A right rotation transforms it into the tree shown in figure (b), where node has become the root. Note that, because the value of is less than the value of , must become ’s right child. Right rotation means transforming a tree from having the shape in (a) to having the shape in (b).
A left rotation is the opposite process: starting from the tree in (b), transforming it to the tree in (a), by lifting node up. Notice that a right rotation tends to make the tree more right-leaning, while a left rotation tends to make it more left-leaning.
11.3.2 AVL tree insertion

Consider what happens when we insert a node with key value 5, as shown in Figure 11.4. The tree on the left meets the AVL tree balance requirements. After the insertion, two nodes no longer meet the requirements. Because the original tree met the balance requirement, nodes in the new tree can only be unbalanced by a difference of at most 2 in the subtrees. For the bottommost unbalanced node, call it , there are 4 cases:
- The extra node is in the left child of the left child of .
- The extra node is in the right child of the left child of .
- The extra node is in the left child of the right child of .
- The extra node is in the right child of the right child of .
Cases 1 and 4 are symmetric, as are cases 2 and 3. Note also that the unbalanced nodes must be on the path from the root to the newly inserted node.
Our problem now is how to balance the tree in time. It turns out that we can do this using a series of rotations. Cases 1 and 4 can be fixed using a single rotation, as shown in Figure 11.5. Cases 2 and 3 can be fixed using a double rotation, as shown in Figure 11.6.


AVL tree insertion and deletion are easiest to implement as recursive functions. They are very similar to the normal BST insertion and deletion, but as the recursion unwinds up the tree, we perform the appropriate rotation on any node that is found to be unbalanced.
Example: AVL insertion
In Figure 11.4 (b), the bottom-most unbalanced node has value 7. The excess node (with value 5) is in the right subtree of the left child of 7, so we have an example of Case 2. This requires a double rotation to fix. After the rotation, 5 becomes the left child of 24, 2 becomes the left child of 5, and 7 becomes the right child of 5.
To try out AVL insertion yourself and see how it works, see the AVL tree visualisation here: https://chalmersgu-data-structure-courses.github.io/dsvis/collections.html
Here is an implementation of AVL trees:
datatype AVLNode of K, V extends BSTNode:
// All properties of the BSTNode
...key, value, left, right Int = 1 // The height of this subtree.
height:
datatype AVLMap extends BSTMap:
size // All properties of the BSTMap
...root, get(key) // Inherited from BSTMap
...
put(key, value):
// Add a key-value pair, or update the value associated with an existing key.
// This is the same as BSTMap.put, except that it rebalances the node afterwards.
= putHelper(root, key, value)
root
putHelper(node, key, value):// Recursive helper method for 'put', returns the updated node.
if node is null:
size = size + 1
return new AVLNode(key, value)
else if key < node.key:
= putHelper(node.left, key, value)
node.left
updateHeight(node)else if key > node.key:
= putHelper(node.right, key, value)
node.right
updateHeight(node)else: // key == node.key
= value
node.value return rebalance(node)
remove(key):
// Delete a key and its associated value.
// This is the same as BSTMap.remove, except that it rebalances the node afterwards.
= removeHelper(root, key)
root
removeHelper(node, key):// Helper method for 'remove', returns the updated node.
if node is null:
return null
else if key < node.key:
= removeHelper(node.left, key)
node.left
updateHeight(node)return rebalance(node)
else if key > node.key:
= removeHelper(node.right, key)
node.right
updateHeight(node)return rebalance(node)
else: // key == node.key
if node.left is null:
size = size - 1
return node.right
else if node.right is null:
size = size - 1
return node.left
else:
= largestNode(node.left)
predecessor = node.value
old_value = predecessor.key
node.key = predecessor.value
node.value = removeHelper(node.left, predecessor.key)
node.left
updateHeight(node)return rebalance(node)
Here are some helper functions:
getHeight(node):// Return the height of a tree node, also works if the node is null.
if node is null:
return 0
else:
return node.height
updateHeight(node):// Recompute the value of the height of a tree node.
// Must be called every time the height could change.
= 1 + max(getHeight(node.left), getHeight(node.right))
node.height
heightDiff(node):// Return the height difference of a node: left height - right height.
return getHeight(node.left) - getHeight(node.right)
largestNode(node):// Find the descendant node having the largest key.
while node.right is not null:
= node.right
node return node
rebalance(node):if node is null:
return null
= heightDiff(node)
diff if diff > 1: // Left-leaning
= heightDiff(node.left)
leftDiff if leftDiff == -1: // Left-right: convert to left-left
= rotateLeft(node.left)
node.left
updateHeight(node)return rotateRight(node)
else if diff < -1: // Right-leaning
= heightDiff(node.right)
rightDiff if rightDiff == 1: // Right-left: convert to right-right
= rotateRight(node.right)
node.right
updateHeight(node)return rotateLeft(node)
else:
return node
rotateLeft(x):// Left rotation.
""" x y
( / \ / \
A y ===> x C
/ \ / \
B C A B """)
// Variables are named according to the picture above.
= x.right
y = y.left
B = B
x.right = x
y.left return y
rotateRight(x):// Right rotation.
""" x y
( / \ / \
y C ===> A x
/ \ / \
A B B C """)
// Variables are named according to the picture above.
= x.left
y = y.right
B = B
x.left = x
y.right return y