AVL Trees
AVL Trees
The topmost node of the tree is called the root, and the nodes below it are
called the child nodes. Each node can have multiple child nodes, and these
child nodes can also have their own child nodes, forming a recursive
structure.
Binary Tree is a non-linear data structure where each node has at most
two children.
A full binary tree is a binary tree with either zero or two child nodes for each
node.
Every non-leaf node has just one child in a binary tree known as
a Degenerate Binary tree.
A complete binary tree is a special type of binary tree where all the levels of
the tree are filled completely except the lowest level nodes which are filled
from as left as possible.
A perfect binary tree is a special type of binary tree in which all the leaf
nodes are at the same depth, and all non-leaf nodes have two children. In
simple terms, this means that all leaf nodes are at the maximum depth of the
tree, and the tree is completely filled with no gaps.
The height of the left and right tree for any node does not differ by more
than 1.
The left subtree of that node is also balanced.
The right subtree of that node is also balanced.
Output:
Inorder Traversal: 10 20 30 100 150 200 300
Preorder Traversal: 100 20 10 30 200 150 300
Postorder Traversal: 10 30 20 150 300 200 100
Input:
Output:
Inorder Traversal: 8 12 20 22 25 30 40
Preorder Traversal: 22 12 8 20 30 25 40
Postorder Traversal: 8 20 12 25 40 30 22
AVL Tree:
An AVL tree defined as a self-balancing Binary Search Tree (BST) where
the difference between heights of left and right subtrees for any node cannot
be more than one.
The difference between the heights of the left subtree and the right subtree
for any node is known as the balance factor of the node.
The AVL tree is named after its inventors, Georgy Adelson-Velsky and
Evgenii Landis, who published it in their 1962 paper “An algorithm for the
organization of information”.
The above tree is AVL because the differences between the heights of left
and right subtrees for every node are less than or equal to 1.
Example of a Tree that is NOT an AVL Tree:
The above tree is not AVL because the differences between the heights of
the left and right subtrees for 8 and 12 are greater than 1.
Most of the BST operations (e.g., search, max, min, insert, delete.. etc)
take O(h) time where h is the height of the BST. The cost of these
operations may become O(n) for a skewed Binary tree. If we make sure
that the height of the tree remains O(log(n)) after every insertion and
deletion, then we can guarantee an upper bound of O(log(n)) for all these
operations. The height of an AVL tree is always O(log(n)) where n is the
number of nodes in the tree.
Left-Right Rotation:
A left-right rotation is a combination in which first left rotation takes place
after that right rotation executes.
Right-Left Rotation:
A right-left rotation is a combination in which first right rotation takes place
after that left rotation executes.
T1, T2 and T3 are subtrees of the tree, rooted with y (on the left
side) or x (on the right side)
y x
/ \ Right Rotation / \
x T3 - - - - - - - > T1 y
/ \ < - - - - - - - / \
T1 T2 Left Rotation T2 T3
z y
/ \ / \
T1 y Left Rotate(z) z x
/ \ - - - - - - - -> / \ / \
T2 x T1 T2 T3 T4
/ \
T3 T4
z z x
/ \ / \ / \
T1 y Right Rotate (y) T1 x Left Rotate(z) z y
/ \ - - - - - - - - -> / \ - - - - - - - -> / \ / \
x T4 T2 y T1 T2 T3 T4
/ \ / \
T2 T3 T3 T4
Illustration of Insertion at AVL Tree
Approach:
The idea is to use recursive BST insert, after insertion, we get pointers to all
ancestors one by one in a bottom-up manner. So we don‟t need a parent
pointer to travel up. The recursive code itself travels up and visits all the
ancestors of the newly inserted node.
/* Helper function that allocates a new node with the given key and
NULL left and right pointers. */
struct Node* newNode(int key)
{
struct Node* node = (struct Node*)
malloc(sizeof(struct Node));
node->key = key;
node->left = NULL;
node->right = NULL;
node->height = 1; // new node is initially added at leaf
return(node);
}
// Perform rotation
x->right = y;
y->left = T2;
// Update heights
y->height = max(height(y->left),
height(y->right)) + 1;
x->height = max(height(x->left),
height(x->right)) + 1;
// Perform rotation
y->left = x;
x->right = T2;
// Update heights
x->height = max(height(x->left),
height(x->right)) + 1;
y->height = max(height(y->left),
height(y->right)) + 1;
return 0;
}
Output
Preorder traversal of the constructed AVL tree is
30 20 10 25 40 50
Complexity Analysis
Time Complexity: O(log(n)), For Insertion
Auxiliary Space: O(1)
The rotation operations (left and right rotate) take constant time as only a
few pointers are being changed there. Updating the height and getting the
balance factor also takes constant time. So the time complexity of the AVL
insert remains the same as the BST insert which is O(h) where h is the
height of the tree. Since the AVL tree is balanced, the height is O(Logn). So
time complexity of AVL insert is O(Logn).
Steps to follow for deletion.
To make sure that the given tree remains AVL after every deletion, we must
augment the standard BST delete operation to perform some re-balancing.
Following are two basic operations that can be performed to re-balance a
BST without violating the BST property (keys(left) < key(root) < keys(right)).
1. Left Rotation
2. Right Rotation
T1, T2 and T3 are subtrees of the tree rooted with y (on left side)
or x (on right side)
y x
/ \ Right Rotation / \
x T3 – - – - – - – > T1 y
/ \ < - - - - - - - / \
T1 T2 Left Rotation T2 T3
Keys in both of the above trees follow the following order
keys(T1) < key(x) < keys(T2) < key(y) < keys(T3)
So BST property is not violated anywhere.
z z x
/ \ / \ / \
y T4 Left Rotate (y) x T4 Right Rotate(z) y z
/ \ - - - - - - - - -> / \ - - - - - - - -> / \ / \
T1 x y T3 T1 T2 T3 T4
/ \ / \
T2 T3 T1 T2
z y
/ \ / \
T1 y Left Rotate(z) z x
/ \ - - - - - - - -> / \ / \
T2 x T1 T2 T3 T4
/ \
T3 T4
d) Right Left Case
z z x
/ \ / \ / \
T1 y Right Rotate (y) T1 x Left Rotate(z) z y
/ \ - - - - - - - - -> / \ - - - - - - - -> / \ / \
x T4 T2 y T1 T2 T3 T4
/ \ / \
T2 T3 T3 T4
/* Helper function that allocates a new node with the given key and
NULL left and right pointers. */
struct Node* newNode(int key)
{
struct Node* node = (struct Node*)
malloc(sizeof(struct Node));
node->key = key;
node->left = NULL;
node->right = NULL;
node->height = 1; // new node is initially added at leaf
return(node);
}
// Perform rotation
x->right = y;
y->left = T2;
// Update heights
y->height = max(height(y->left), height(y->right))+1;
x->height = max(height(x->left), height(x->right))+1;
// Perform rotation
y->left = x;
x->right = T2;
// Update heights
x->height = max(height(x->left), height(x->right))+1;
y->height = max(height(y->left), height(y->right))+1;
return current;
}
// Recursive function to delete a node with given key
// from subtree with given root. It returns root of
// the modified subtree.
struct Node* deleteNode(struct Node* root, int key)
{
// STEP 1: PERFORM STANDARD BST DELETE
if (root == NULL)
return root;
// No child case
if (temp == NULL)
{
temp = root;
root = NULL;
}
else // One child case
*root = *temp; // Copy the contents of
// the non-empty child
free(temp);
}
else
{
// node with two children: Get the inorder
// successor (smallest in the right subtree)
struct Node* temp = minValueNode(root->right);
return root;
}
return 0;
}
Output:
Preorder traversal of the constructed AVL tree is
9 1 0 -1 5 2 6 10 11
Preorder traversal after deletion of 10
1 0 -1 9 5 2 6 11
Time Complexity: The rotation operations (left and right rotate) take
constant time as only few pointers are being changed there. Updating the
height and getting the balance factor also take constant time. So the time
complexity of AVL delete remains same as BST delete which is O(h) where h
is height of the tree. Since AVL tree is balanced, the height is O(Logn). So
time complexity of AVL delete is O(Log n).
Auxiliary Space: O(1), since no extra space is used.
Advantages Of AVL Trees
It is always height balanced
Height Never Goes Beyond LogN, where N is the number of nodes
It give better search than compared to binary search tree
It has self balancing capabilities
Summary of AVL Trees
These are self-balancing binary search trees.
Balancing Factor ranges -1, 0, and +1.
When balancing factor goes beyond the range require rotations to be
performed
Insert, delete, and search time is O(log N).
AVL tree are mostly used where search is more frequent compared to
insert and delete operation.
Searching:
#include <stdio.h>
#include <stdlib.h>
struct Node {
int key;
struct Node* left;
struct Node* right;
};
// Driver Code
int main()
{
// Creating a hard coded tree for keeping
// the length of the code small. We need
// to make sure that BST properties are
// maintained if we try some other cases.
struct Node* root = newNode(50);
root->left = newNode(30);
root->right = newNode(70);
root->left->left = newNode(20);
root->left->right = newNode(40);
root->right->left = newNode(60);
root->right->right = newNode(80);
Output
Not Found
Found
struct Node {
int key;
struct Node* left;
struct Node* right;
};
// Driver Code
int main() {
return 0;
}
Output
Not Found
Found
temp->data = data;
temp->left = temp->right = NULL;
return temp;
}
return root;
}
// Driver code
int main()
{
int pre[] = { 10, 5, 1, 7, 40, 50 };
int size = sizeof(pre) / sizeof(pre[0]);
// Function call
struct node* root = constructTree(pre, size);
printInorder(root);
return 0;
}
Output
Inorder traversal of the constructed tree:
1 5 7 10 40 50