An AVL (Adelson-Velskii and Landis) tree is practically a binary search tree with the requirement that, for every node in the tree, the height of the left and right subtrees can differ by at most 1. An AVL tree with just one node will have a height of 1, thus, an empty AVL tree will have a height of either -1 or 0.
The insertion and deletion operations for an AVL tree are, unfortunately, not as easy to understand or implement as the ones for a binary search tree. We add the new node to the AVL tree similarly, but then we have to balance the tree to meet the set requirement regarding heights. We perform rotation operations to balance the tree.
The four rotation operations, and thus our four programming goals for insertion, are:
Goals 1 and 4 are practically the same operation but mirrored. The same can be said for goals 2 and 3.
Before moving on to the rest of the Answer, we should look at the two files in the widget below. We'll use the class templates in our implementation.
A glance at AVL.hpp
shows that every node
in our AVL tree will have a key
, left and right nodes, and height
. We also have a constructor that will set every new node's height
to 1. We then have the AVL
class. It has a constructor, destructor, and helper methods which are both necessary for the AVL tree to function and used later in our final main.cpp
for testing purposes. We will only be discussing the methods listed below in line 37.
#include "AVL.cpp"#ifndef __AVL_HPP#define __AVL_HPP// This struct acts as a node to be used in the AVL tree.template <class T>struct node{T key;node *left;node *right;int height;// This is the default constructor for the node.node (T key){this->key = key;left = NULL;right = NULL;height = 1;}};// The class for the AVL tree.template <class T>class AVL {node<T> *root;public:// Required for the AVL tree to function.AVL();~AVL();void postEmpty(node<T>* currNode);void deleteNode(node<T>* &currNode, T k);node<T>* FindMin(node<T>* currNode);node<T>* getRoot();// What we will implement.void insertNode(node<T>* &currNode, node<T>* newNode);int height (node<T>* p);int heightMax(node<T>* leftNode, node<T>* rightNode);void rotateLeft(node<T>* &currNode);void rotateRight(node<T>* &currNode);void doubleLeft(node<T>* &currNode);void doubleRight(node<T>* &currNode);void balance(node<T>* &currNode);};#endif
First of all, let's implement the basic insertion operation (same as the one for binary search trees).
template<class T>void AVL<T>::insertNode(node<T>* &currNode, node<T>* newNode) {if(currNode == NULL) {currNode = newNode;} else if(newNode->key < currNode->key) {insertNode(currNode->left, newNode);} else if(newNode->key > currNode->key) {insertNode(currNode->right, newNode);}balance(currNode);}
This is a fairly simple function:
height
and heightMax
functionOur balance
and rotation methods are going to need to know the heights of nodes in order to actually check for them and decide on the rotation to be used. Luckily, we were making our nodes store values for height
. Thankfully, getting the height of a node in an AVL tree is done the same way as in a binary search tree:
template <class T>int AVL<T> :: height (node<T>* p) {int left = 0;int right = 0;if(p == NULL) {return 0;}left = height(p->left);right = height(p->right);if(left >= right) {// The plus-1 is due to the node// itself having a height of 1.return (left + 1);} else {return (right + 1);}}
As we are going to be comparing the heights of nodes a lot, it is best to create a helper method to find the maximum height between two nodes:
template <class T>int AVL<T>::heightMax(node<T>* leftNode, node<T>* rightNode) {if(height(leftNode) >= height(rightNode)) {return height(leftNode);} else if(height(leftNode) < height(rightNode)) {return height(rightNode);}return 0;}
balance
methodWith our helpers ready, let's make the balance
method. In order to create the balance
method, we must keep the above mentioned goals in mind. Those goals directly translate to which rotations have to be made and where.
template<class T>void AVL<T>::balance(node<T>* &currNode) {if(currNode == NULL) {return;}if(height(currNode->left) - height(currNode->right) > 1) {if(height(currNode->left->left) >= height(currNode->left->right)) {rotateRight(currNode);} else {doubleRight(currNode);}} else if(height(currNode->right) - height(currNode->left) > 1) {if(height(currNode->right->right) >= height(currNode->right->left)) {rotateLeft(currNode);} else {doubleLeft(currNode);}}currNode->height = heightMax(currNode->left, currNode->right) + 1;}
currNode
's left and right nodes. If the difference is greater than 1 (can only be -1, 0, and 1), we go in and balance. We then check which type of left-rotation operation needs to be performed, on currNode
, by applying the two goals as a condition.currNode
’s right and left nodes. If the difference is greater than 1 (can only be -1, 0, and 1), we go in and balance. We then check which type of right-rotation operation needs to be performed, on currNode
, by applying the two goals as a condition.currNode
using heightMax
.Note: The maximum height difference, thanks to the way both
insertNode
andbalance
work, will rarely ever be greater than 2. We always balance the tree at all places whenever we add a new node.
We now just have our rotation operations left. We'll create the methods for left-rotation operations. Try a shot at right-rotation operations (or find them in the final section).
template<class T>void AVL<T>::rotateLeft(node<T>* &currNode) {node<T>* rightChild = currNode->right;currNode->right = rightChild->left;rightChild->left = currNode;currNode->height = heightMax(currNode->left, currNode->right);rightChild->height = heightMax(rightChild->right, currNode);currNode = rightChild;}
See the rotateLeft
method in the illustration below:
To handle for goal three, we first right-rotate the right child (of the offending node) with its left child, and then left-rotate the parent node with the now new right child. Let's look at an illustration:
Hopefully, the diagram above made things easier to understand. We can take a shortcut with the double-left rotation like so:
template<class T>void AVL<T>::doubleLeft(node<T>* &currNode) {rotateRight(currNode->right);rotateLeft(currNode);}
Putting all of the newly created methods together into the AVL.cpp
and creating a main.cpp
for testing will result in the code block below. Feel free to play around with the code, especially main.cpp
, to see whether AVL trees created by this implementation match your own.
#include <iostream>#include <cstdio>#include <cstdlib>#include "AVL.hpp"using namespace std;int values[25] = {55, 43, 26, 82, 93, 4, 39, 95, 50, 6, 62, 17, 21, 49, 77, 5, 32, 60, 88, 16, 44, 72, 80, 8, 36};void test_addition(){cout << "Testing Addition:\n";// Make an AVL tree that takes nodes// which have int values.AVL<int> Tree;node<int>* root = Tree.getRoot();// Populate the tree with nodes.for (int i = 0; i < 25; i++) {Tree.insertNode(root, new node<int>(values[i]));}// After tree is populated, check whether// the nodes are where they should belong.bool flag = false;if(root->key != 43) { flag = true; }if(root->right->key != 62) { flag = true; }if(root->left->key != 17) { flag = true; }if(root->left->left->left->key != 4) { flag = true; }if(root->left->right->right->key != 36) { flag = true; }if(root->right->left->right->key != 60) { flag = true; }if(root->right->right->right->right->key != 95) { flag = true; }if(root->left->left->right->left->key != 6) { flag = true; }if(flag) {cout << "Addition Test Failed!\n";} else {cout << "Addition Test Passed!\n";}cout << root->key << endl;cout << root->right->key << endl;cout << root->right->right->right->right->key << endl;cout << root->left->left->right->left->key << endl;}int main(){test_addition();}
Free Resources