An AVL tree, named after Adelson Velsky and Landis, is a self-balancing binary search tree.

Properties

Since the AVL tree is a BST, it shares many of its properties. The main difference in between them is that the AVL tree is self-balancing. Since the AVL tree is always balanced, it has a maximum height of where n is the number of nodes. To do so, the AVL tree is defined by a balance factor.

Balance Factor

The balance factor at any node of an AVL tree is the difference in height in between its left subtree and its right subtree. A balanced tree has at most a balance factor of for every node in its tree. For example, try determining which of these is balanced:

Implementation

One of the possible implementations for a node of such a tree could be something like this:

enum balance_type{ LEFT_HEAVY = -1, BALANCED = 0, RIGHT_HEAVY = 1};
 
class AVLNode {
	public: 
		int data;
		int height;
		AVLNode* left;
		AVLNode* right;
		balance_type balance;
}

Where it effectively just has extra fields that denote the balance of that node’s subtree and its height. It would also include rotations, which are they key operations that keep the AVL tree balanced.

Rotations

Balancing of the tree occurs via operations called rotations. Assuming we take the above implementation example, how would we implement rotations so that our tree self-balances correctly? To rebalance an AVL tree, we must have an algorithm that handles all types of imbalances. There are four different types of imbalance, each with their own algorithm to rebalance the tree or subtree.

LEFT LEFT

Left left imbalance is when the source of imbalance is the subtree’s left child’s left child. It looks like this:

And is solved by a right rotation at C. What would do you think the resulting tree should look like?

This can be implemented with an algorithm like the following:

Node* rightRotate(Node*& n) {
    Node* newSubRoot = n->left; //the new subroot is the roots left child
    n->left = newSubRoot->right;
    //if B had a right subtree, we would shift that to the original root's left child
    newSubRoot->right = n; //shift the old root to the new ones right
    n = newSubRoot;
 
    // Update heights
    n->height = max(height(n->left), height(n->right)) + 1;
    newSubRoot->height = max(height(newSubRoot->left), height(newSubRoot->right)) + 1;
 
    // New root
    return newSubRoot;
}

RIGHT RIGHT

Right right imbalance looks like this:

And is solved by a left rotation at A. The resulting tree looks the same as the previous one. The algorithm is effectively the same but with the left/right nodes swapped.

Node* leftRotate(Node*& n) {
    Node* newSubRoot = n->right; //the new subroot is the roots right child
    n->right = newSubRoot->left;
    //if B had a left subtree, we would shift that to the original root's (As) right child
    newSubRoot->left = n; //shift the old root to the new ones right
    n = newSubRoot;
 
    // Update heights
    n->height = max(height(n->right), height(n->left)) + 1;
    newSubRoot->height = max(height(newSubRoot->right), height(newSubRoot->left)) + 1;
 
    // New root
    return newSubRoot;
}

LEFT RIGHT

Left-right imbalance is when the source of imbalance is the subtree’s left child’s right child.

It is solved by a left rotation at A, then a right rotation at C. Note that after the left rotation at A, we effectively get the LEFT LEFT case above.

RIGHT LEFT

This imbalance is solved by a right rotation at C, then a left rotation at A. Note that after the right rotation at C, we effectively get the RIGHT RIGHT case above.

Tip

It’s pretty likely you won’t have to memorize the exact algorithm for rotations or implementations of an AVL tree. However, you do need to understand how it works and how to trace AVL insertions.

Insertion

One of the most effective ways of ensuring AVL balance is to never let it become unbalanced upon insertion in the first place.

Insertion starts with a regular BST insertion, followed by rotations to maintain balance. In other words, we maintain the properties of an AVL tree after each insertion.

One of the best ways to check your own understanding on this topic is to see if you can predict what an AVL tree would look like after a certain number of random insertions. Try your hand at predicting what an AVL tree that was initially empty would look like after insertions of 1, 3, 5, 7, 9, 8, 6, 4, 2.

Common Problems

One common type of problem you’re likely to encounter is how to transform a degenerate binary tree (in this class they also sometimes call it a spine or a stick) into a proper AVL tree. Try to figure this one out for yourself.