Abstract data type:
Data_Structure Tree Binary_Tree Balanced_Tree Binary_Search_Tree AVL_Tree
An AVL (Adelson-Velsky Landis) tree is a self-balancing Binary Search Tree (BST) where the balancing factor (bf) of nodes is in rage of {-1, 0, 1}, which means the difference between heights of left and right subtree for any node cannot be more than one. An AVL tree always have for searching, insertion, and deletion.
Properties
- AVL trees are self-balancing BSTs
- Each node in an AVL tree has at most two children
- The tree has all the properties of a BST with the subtree of a node cannot be greater than the value of the subtree, vice versa.
- Balancing factor (bf)
- The balancing factor is defined as
- In AVL trees, the balancing factor cannot be less than -1 or greater than 1
- for all nodes in the AVL tree
Pros and cons
- Advantage
- Searching, insertion, and deletion are always in time
- The height balancing adds no more than a constant factor to the speed of insertion
- Disadvantage
- Difficult to program and debug
- Extra space for balance factor
- Most large searches are done in database systems uses other structures (e.g. B-tree)
Implementation
Balancing factor (bf)
Balancing factor of a node is calculated as the difference in height between its left and right subtrees. The expression can be described as: Before determine the balancing factor of a node, we need to know the height of a subtree. If a node does not have any child, the height of its subtree is -1 (define the height of an empty tree as -1).
Interpretate balancing factor
Balancing factor (bf) | Description |
---|---|
Perfectly balanced | |
Left subtree is 1 level taller than the right subtree | |
Right subtree is 1 level taller than the left subtree | |
Unbalanced tree, with heavy left subtree | |
Unbalanced tree, with heavy right subtree |
Height and balancing factor
The balancing factor is calculated based on the heights of the subtrees. The heigh of a subtree is equivalent to the height of the root node of that subtree. The height of a node is set to 0 initially when the node is created. As the tree grows and rebalancing operations are performed, the heights of nodes are updated accordingly. (see [[#|insert method]]) The height of a node can be evaluated as: The height of a node is defined as the maximum heigh of its children plus 1.
30
/ \
20 40
/
10
In this example, the heights of the nodes are:
- Node
10
: 0 - Node
20
: 1 - Node
40
: 0 - Node
30
: 2
When a new node 5
is inserted
30
/ \
20 40
/
10
/
5
Heigh of node 5
upon insertion is 0.
Backtrack and update heights:
- Node
10
new height : - Node
20
new height: - Node
30
new height:
Balance the tree: Check balance factors:
- Balance factor of node
30
: - Unbalanced, with heavy left subtree
- Right rotation will be performed (will be discussed next)
class TreeNode {
int value, height;
TreeNode(int val) {
value = val;
height = 0;
}
}
// get height of a tree node
int height(TreeNode node) {
if (n == null)
// height of an empty tree is -1
return -1;
return node.height;
}
void updateHeight(TreeNode node) {
node.height = Math.max(height(node.left), height(node.right)) + 1;
}
int balanceFactor(TreeNode node) {
if (n == null)
return 0;
return height(node.left) - height(node.right);
}
Rotations
The characteristic feature of an AVL tree is the rotation operation. It allows AVL tree to maintain balance while preserving BST property. When a node has a balancing factor , it is an unbalanced node. Depend on what type of imbalance, employ different rotation strategy: right rotation, left rotation, right-left rotation, and left-right rotation.
Right rotation
Right rotation is performed when a node is unbalanced, and has become left heavy (its left subtree is taller than its right subtree for more than 1 level). It takes the root node (unbalanced node) and rotates it to the right.
Before Rotation
30
/
20
/ \
10 null
Node 30
has a balancing factor of 2 (height difference of 2 between its left and right subtree)
We perform the right rotation:
20
: The left child of the root node (30
) becomes the new root- The right child of
20
becomes the left child of the old node (30
) after rotation 10
: The original left child of20
remains as the as the left child of the new root (20
)
After Rotation
20
/ \
10 30
/
null
TreeNode rightRotate(TreeNode node) {
TreeNode child = node.left; // 20
TreeNode grandChild = child.right; // null
// rotate node to the right around child
child.right = node;
node.left = grandChild;
// update heights
updateHeight(node);
updateHeight(child);
// return the new root node
return child;
}
Left rotation
Left rotation is performed when a node is right heavy. It takes the root node and rotates it to the left.
Before Rotation
10
\
20
/ \
15 22
We perform the left rotation:
20
: The right child of the root node (10
) becomes the new root15
: The left child of20
becomes the right child of the old root node (10
) after rotation22
: The original right child of20
remains as the right child of the new root (20
)
After Rotation
20
/ \
10 22
\
15
TreeNode leftRotate(TreeNode node) {
TreeNode child = node.right; // 20
TreeNode grandChild = child.left; // 15
child.left = node;
node.right = grandChild;
updateHeight(node);
updateHeight(child);
return child;
}
Right-left rotation
Right-left rotation is performed when a node is right-heavy (bf < -1) and its right child is left-heavy (bf > 0).
Before Rotation
10
\
30
/
20
We perform the right then left rotation:
- Perform a right rotation on the right child of the root node (
30
) - Perform a left rotation on the unbalanced node (
10
)
Right Rotation
10
\
20
\
30
Left rotation
20
/ \
10 30
node.right = rightRotate(node.right) // 30
leftRotate(node) // 10
Left-right rotation
Left-right rotation is performed when a node is left heavy (bf > 1) and its child is right heavy (bf < 0)
Before Rotation
30
/
20
\
25
We perform the left then right rotation:
- Perform a left rotation of on the left child of the root node (
20
) - Perform a right rotation on the unbalanced node (
30
)
Left Rotation
30
/
25
/
20
Right Rotaion
25
/ \
20 30
node.left = rightRotate(node.left) // 20
leftRotate(node) // 30
Rotation cases
bf of unbalanced node | bf of child node | Choice of rotation |
---|---|---|
(left-heavy tree) | Right rotation | |
(right-heavy tree) | Left rotation | |
(left-heavy tree) | (right-tilted tree) | Left-right rotation |
(right-heavy tree) | (left-tilted tree) | Right-left rotation |
![[Pasted image 20240601015322.png | 600]] | |
The rotation operation can be encapsulated into a single method. |
// perform rotation to restore balance of a bst
TreeNode rotate(TreeNode node) {
// get the balance factor of node
int balanceFactor = balanceFactor(node);
// left-heavy tree
if (balanceFactor > 1) {
if (balanceFactor(node.left) >= 0) {
// only right rotation
return rightRotate(node);
} else {
// left then right rotation
node.left = leftRotate(node.left);
return rightRotate(node);
}
}
// right-heavy tree
if (balanceFactor < -1) {
if (balanceFactor(node.right) <= 0) {
// only left rotation
return leftRotate(node);
} else {
// right then left rotation
node.right = rightRotate(node.right);
return leftRotate(node);
}
}
// balanced tree, no further rotation needed
return node;
}
Insert
The insert operation in AVL trees is similar to BST insertion. The only difference is the AVL tree performs rotation on node after insertion if necessary, and update the height and balancing factors of nodes.
void insert(int val) {
root = insertHelper(root, val);
}
TreeNode insertHelper(TreeNode node, int val) {
//check the root of current subtree
if (node == null)
//indicating an empty subtree, the new node is assigned to //the root of this subtree
return new TreeNode(val);
if (val < node.value)
//update the left child of the current node with the result of
//the insertion operation
node.left = insertHelper(node.left, val);
else if (val > node.value)
node.right = insertHelper(node.right, val);
else
return node; // do not insert duplicate nodes
// update the height
updateHeight(node);
// perform rotation operation to restore balance to the subtree
node = rotate(node);
// return the root node of the subtree
return node;
}
Remove
The remove is inherited from BST remove method, with additional rotation and update attribute operations.
void remove(int val) {
root = removeHelper(root, val);
}
TreeNode removeHelper(TreeNode node, int val) {
if (node == null)
return null;
// find and remove the node
if (val < node.value)
node.left = removeHelper(node.left, val);
else if (val > node.value)
node.right = removeHelper(node.right, val);
else {
if (node.left == null || node.right == null) {
TreeNode child = node.left != null ? node.left : node.right;
if (child == null)
return null;
else
node = child;
} else {
TreeNode temp = node.right;
while (temp.left != null) {
temp = temp.left;
}
node.right = removeHelper(node.right, temp.val);
node.val = temp.val;
}
}
// update node height, restore balance if needed
updateHeight(node);
node = rotate(node);
return node;
}
Back to parent page: Data Structures and Algorithms
Reference