Balanced Binary Tree
Balanced Binary Tree
Overview:
A balanced binary tree is a binary tree in which the depth of the two subtrees of every node never differs by more than one. This ensures that the tree remains relatively balanced, preventing degenerate cases where the tree becomes skewed and the time complexity of operations degrades.
One common type of balanced binary tree is an AVL tree (named after its inventors Adelson-Velsky and Landis). AVL trees use rotations to maintain balance after insertions and deletions. The balancing factor (height difference between left and right subtrees) for each node in an AVL tree must be -1, 0, or 1.
Here's a brief overview of how AVL tree rotations work:
- Left Rotation:
- Used when the right subtree of a node is significantly deeper.
- A left rotation "moves" a portion of the right subtree to the left, promoting the right child of the node as the new root.
- Right Rotation:
- Used when the left subtree of a node is significantly deeper.
- A right rotation "moves" a portion of the left subtree to the right, promoting the left child of the node as the new root.
- Left-Right Rotation (Double Rotation):
- Used when the left subtree of a node has a right-heavy subtree.
- It involves a left rotation on the left child followed by a right rotation on the original node.
- Right-Left Rotation (Double Rotation):
- Used when the right subtree of a node has a left-heavy subtree.
- It involves a right rotation on the right child followed by a left rotation on the original node.
During tree insertion or deletion, these rotations are applied to maintain the balance property. AVL trees guarantee logarithmic time complexity for insertion, deletion, and search operations.
It's important to note that while AVL trees ensure balance, they may have higher constant factors in practice compared to simpler binary search trees. For some scenarios, Red-Black trees might be preferred due to their slightly relaxed balancing constraints, making them faster for certain operations. The choice depends on specific use cases and performance requirements.
Example Python Code:
class Node:
def __init__(self, key):
self.key = key
self.height = 1
self.left = None
self.right = None
def height(node):
if node is None:
return 0
return node.height
def update_height(node):
node.height = 1 + max(height(node.left), height(node.right))
def balance_factor(node):
if node is None:
return 0
return height(node.left) - height(node.right)
def rotate_right(y):
x = y.left
T2 = x.right
x.right = y
y.left = T2
update_height(y)
update_height(x)
return x
def rotate_left(x):
y = x.right
T2 = y.left
y.left = x
x.right = T2
update_height(x)
update_height(y)
return y
def balance(node):
if node is None:
return node
update_height(node)
# Get the balance factor of this node
factor = balance_factor(node)
# Left Heavy
if factor > 1:
# Left-Right Case
if balance_factor(node.left) < 0:
node.left = rotate_left(node.left)
# Left-Left Case
return rotate_right(node)
# Right Heavy
if factor < -1:
# Right-Left Case
if balance_factor(node.right) > 0:
node.right = rotate_right(node.right)
# Right-Right Case
return rotate_left(node)
return node
def insert(root, key):
# Standard BST insert
if root is None:
return Node(key)
if key < root.key:
root.left = insert(root.left, key)
elif key > root.key:
root.right = insert(root.right, key)
else:
return root # Duplicate keys are not allowed
# Update height of current node
update_height(root)
# Rebalance the tree
return balance(root)
def inorder_traversal(node):
if node:
inorder_traversal(node.left)
print(node.key, end=' ')
inorder_traversal(node.right)
# Example usage:
root = None
keys_input = input()
# Split the input string by spaces to get individual keys
keys = list(map(int, keys_input.split()))
for key in keys:
root = insert(root, key)
print("Inorder Traversal of AVL tree:")
inorder_traversal(root)
Step-By-Step Explanation:
- Node Class: Defines a simple
Node
class for the AVL tree. Each node has akey
(the value of the node), aheight
(to track the height of the subtree rooted at this node), and pointers to the left and right children. - Height Function: Returns the height of a given node. If the node is
None
, the height is 0. - Update Height Function: Updates the height of a given node based on the heights of its left and right children.
- Balance Factor Function: Calculates the balance factor of a node, which is the difference between the height of the left subtree and the height of the right subtree.
- Rotate Right Function: Performs a right rotation on the given nodes
x
andy
. Used during balancing to address left-heavy situations. - Rotate Left Function: Performs a left rotation on the given nodes
x
andy
. Used during balancing to address right-heavy situations. - Balance Function: Balances the AVL tree by checking the balance factor of the given node and performs necessary rotations (left or right) to maintain the AVL property.
- Insert Function: Inserts a new key into the AVL tree while maintaining the binary search tree (BST) property. After insertion, it updates the height and balances the tree.
- Inorder Traversal Function: Performs an inorder traversal of the AVL tree and prints the keys in sorted order.
Example c++ code:
#include <iostream>
using namespace std;
// Node structure for the AVL tree
struct Node {
int key;
int height;
Node* left;
Node* right;
Node(int k) : key(k), height(1), left(nullptr), right(nullptr) {}
};
// Function to get the height of a node
int height(Node* node) {
return (node == nullptr) ? 0 : node->height;
}
// Function to update the height of a node based on its children's heights
void updateHeight(Node* node) {
node->height = 1 + max(height(node->left), height(node->right));
}
// Function to calculate the balance factor of a node
int balanceFactor(Node* node) {
return (node == nullptr) ? 0 : height(node->left) - height(node->right);
}
// Function to perform a right rotation on the given nodes x and y
Node* rotateRight(Node* y) {
Node* x = y->left;
Node* T2 = x->right;
x->right = y;
y->left = T2;
updateHeight(y);
updateHeight(x);
return x;
}
// Function to perform a left rotation on the given nodes x and y
Node* rotateLeft(Node* x) {
Node* y = x->right;
Node* T2 = y->left;
y->left = x;
x->right = T2;
updateHeight(x);
updateHeight(y);
return y;
}
// Function to balance the AVL tree starting from the given node
Node* balance(Node* node) {
if (node == nullptr) {
return node;
}
updateHeight(node);
// Get the balance factor of this node
int factor = balanceFactor(node);
// Left Heavy
if (factor > 1) {
// Left-Right Case
if (balanceFactor(node->left) < 0) {
node->left = rotateLeft(node->left);
}
// Left-Left Case
return rotateRight(node);
}
// Right Heavy
if (factor < -1) {
// Right-Left Case
if (balanceFactor(node->right) > 0) {
node->right = rotateRight(node->right);
}
// Right-Right Case
return rotateLeft(node);
}
return node;
}
// Function to insert a key into the AVL tree
Node* insert(Node* root, int key) {
// Standard BST insert
if (root == nullptr) {
return new Node(key);
}
if (key < root->key) {
root->left = insert(root->left, key);
} else if (key > root->key) {
root->right = insert(root->right, key);
} else {
return root; // Duplicate keys are not allowed
}
// Update height of the current node
updateHeight(root);
// Rebalance the tree
return balance(root);
}
// Function to perform an inorder traversal of the AVL tree
void inorderTraversal(Node* root) {
if (root) {
inorderTraversal(root->left);
cout << root->key << " ";
inorderTraversal(root->right);
}
}
// Main function for testing
int main() {
Node* root = nullptr;
std::string keys_input;
// std::cout << "Enter the keys separated by spaces: ";
std::getline(std::cin, keys_input);
// Create a stringstream from the input string
std::stringstream ss(keys_input);
int key;
std::vector<int> keys;
// Extract each key from the stringstream and store it in the keys vector
while (ss >> key) {
keys.push_back(key);
}
for (int key : keys) {
root = insert(root, key);
}
cout << "Inorder Traversal of AVL tree:" << endl;
inorderTraversal(root);
return 0;
}
Step-By-Step Explanation:
- Node Structure: Defines a structure for a node in the AVL tree. Each node contains a key, height, and pointers to the left and right children.
- Height Function: Returns the height of a node. If the node is
nullptr
, the height is 0. - Update Height Function: Updates the height of a given node based on the heights of its left and right children.
- Balance Factor Function: Calculates the balance factor of a node, which is the difference between the height of the left subtree and the height of the right subtree.
- Rotate Right and Rotate Left Functions: Perform right and left rotations on the given nodes, respectively.
- Balance Function: Balances the AVL tree by checking the balance factor of the given node and performs necessary rotations (left or right) to maintain the AVL property.
- Insert Function: Inserts a new key into the AVL tree while maintaining the binary search tree (BST) property. After insertion, it updates the height and balances the tree.
- Inorder Traversal Function: Performs an inorder traversal of the AVL tree and prints the keys in sorted order.
- Main Function: Creates an AVL tree and inserts keys
[10, 20, 30, 40, 50, 25]
. Then, it prints the inorder traversal to demonstrate that the AVL tree is balanced.
Example Java code:
import java.util.Scanner;
class Node {
int key;
int height;
Node left, right;
public Node(int item) {
key = item;
height = 1;
left = right = null;
}
}
// AVL tree class
class AVLTree {
Node root;
// Function to get the height of a node
int height(Node node) {
return (node == null) ? 0 : node.height;
}
// Function to update the height of a node based on its children's heights
void updateHeight(Node node) {
node.height = 1 + Math.max(height(node.left), height(node.right));
}
// Function to calculate the balance factor of a node
int balanceFactor(Node node) {
return (node == null) ? 0 : height(node.left) - height(node.right);
}
// Function to perform a right rotation on the given nodes x and y
Node rotateRight(Node y) {
Node x = y.left;
Node T2 = x.right;
x.right = y;
y.left = T2;
updateHeight(y);
updateHeight(x);
return x;
}
// Function to perform a left rotation on the given nodes x and y
Node rotateLeft(Node x) {
Node y = x.right;
Node T2 = y.left;
y.left = x;
x.right = T2;
updateHeight(x);
updateHeight(y);
return y;
}
// Function to balance the AVL tree starting from the given node
Node balance(Node node) {
if (node == null) {
return node;
}
updateHeight(node);
// Get the balance factor of this node
int factor = balanceFactor(node);
// Left Heavy
if (factor > 1) {
// Left-Right Case
if (balanceFactor(node.left) < 0) {
node.left = rotateLeft(node.left);
}
// Left-Left Case
return rotateRight(node);
}
// Right Heavy
if (factor < -1) {
// Right-Left Case
if (balanceFactor(node.right) > 0) {
node.right = rotateRight(node.right);
}
// Right-Right Case
return rotateLeft(node);
}
return node;
}
// Function to insert a key into the AVL tree
Node insert(Node root, int key) {
// Standard BST insert
if (root == null) {
return new Node(key);
}
if (key < root.key) {
root.left = insert(root.left, key);
} else if (key > root.key) {
root.right = insert(root.right, key);
} else {
return root; // Duplicate keys are not allowed
}
// Update height of the current node
updateHeight(root);
// Rebalance the tree
return balance(root);
}
// Function to perform an inorder traversal of the AVL tree
void inorderTraversal(Node root) {
if (root != null) {
inorderTraversal(root.left);
System.out.print(root.key + " ");
inorderTraversal(root.right);
}
}
// Main method for testing
public static void main(String[] args) {
AVLTree tree = new AVLTree();
Scanner scanner = new Scanner(System.in);
// Prompt the user to enter the keys separated by spaces
// System.out.print("Enter the keys separated by spaces: ");
String keysInput = scanner.nextLine();
// Split the input string by spaces to get individual keys
String[] keysInputArray = keysInput.split(" ");
int[] keys = new int[keysInputArray.length];
// Parse each key from the input array
for (int i = 0; i < keysInputArray.length; i++) {
keys[i] = Integer.parseInt(keysInputArray[i]);
}
for (int key : keys) {
tree.root = tree.insert(tree.root, key);
}
System.out.println("Inorder Traversal of AVL tree:");
tree.inorderTraversal(tree.root);
}
}
Step-By-Step Explanation:
- Node Class: Defines a class for a node in the AVL tree. Each node contains a key, height, and pointers to the left and right children.
- AVLTree Class: Defines the AVL tree class with methods for height calculation, height updating, balance factor calculation, rotation operations, balancing, insertion, and inorder traversal.
- Height Function: Returns the height of a node. If the node is
null
, the height is 0. - Update Height Function: Updates the height of a given node based on the heights of its left and right children.
- Balance Factor Function: Calculates the balance factor of a node, which is the difference between the height of the left subtree and the height of the right subtree.
- Rotate Right and Rotate Left Functions: Perform right and left rotations on the given nodes, respectively.
- Balance Function: Balances the AVL tree by checking the balance factor of the given node and performs necessary rotations (left or right) to maintain the AVL property.
- Insert Function: Inserts a new key into the AVL tree while maintaining the binary search tree (BST) property. After insertion, it updates the height and balances the tree.
- Inorder Traversal Function: Performs an inorder traversal of the AVL tree and prints the keys in sorted order.
- Main Method: Creates an AVL tree and inserts keys
[10, 20, 30, 40, 50, 25]
. Then, it prints the inorder traversal to demonstrate that the AVL tree is balanced.
Real-World Applications of Balancing Binary Tree:
- Databases:
- Indexing: Balancing binary trees are used to implement indexes in databases. They allow for efficient search and retrieval of data, reducing the time complexity of operations like searching for a specific record.
- File Systems:
- File System Indexing: Similar to databases, balanced binary trees are used in file systems to index and organize files efficiently. This speeds up file retrieval operations and helps maintain a hierarchical structure.
- Networking:
- Routing Tables: In networking, balanced binary trees are employed to implement routing tables. The hierarchical nature of these trees allows for quick and efficient lookup of routes in large-scale networks.
- Compilers:
- Symbol Tables: Compilers use symbol tables to manage identifiers (variables, functions, etc.) in a program. Balanced binary trees help in quickly searching and updating these symbol tables during the compilation process.
- Memory Management:
- Memory Allocation Tracking: In memory management systems, AVL trees can be used to track allocated memory blocks efficiently. This helps in quickly locating free blocks and managing memory allocation and deallocation.
- Caches:
- Cache Management: In caching mechanisms, balanced binary trees can be used to manage and organize cached data efficiently. This facilitates quick searches and updates in the cache, improving overall performance.
- Dynamic Order Statistics:
- Priority Queues: Balanced binary trees are used to implement priority queues, which are essential in various applications like task scheduling and managing events with different priorities.
- Collaborative Filtering (Recommendation Systems):
- User Item Matrix: In recommendation systems, where there is a need to efficiently look up user preferences or item ratings, balanced binary trees can be used to represent user-item matrices for quick search and retrieval.
- Data Deduplication:
- Storage Systems: In storage systems, particularly for data deduplication, balanced binary trees can be used to index and manage unique data blocks, enabling quick identification and elimination of duplicate data.
- Transaction Processing Systems:
- Concurrency Control: In systems dealing with concurrent transactions, balanced binary trees can be used to maintain order and facilitate efficient searching and updating of transaction records.
Test Cases
Test Case 1: Input: "10 20 30 40 50".
Output: The output is the result of an inorder traversal of the AVL tree, which prints the keys in sorted order.
Explanation of Output:
- An inorder traversal of a binary search tree (BST) visits the nodes in ascending order of their keys.
- In this case, the AVL tree's nodes are traversed in sorted order.
- The output "10 20 30 40 50" indicates that the keys of the AVL tree, when traversed in inorder, are sorted as 10, 20, 30, 40, and 50.
- This confirms that the AVL tree maintains its balanced property and correctly orders its keys.
Test Case 2: Input: "5 10 15 20 25 30".
Output: The output is the result of an inorder traversal of the AVL tree, which prints the keys in sorted order.
Explanation of Output:
- An inorder traversal of a binary search tree (BST) visits the nodes in ascending order of their keys.
- In this case, the AVL tree's nodes are traversed in sorted order.
- The output "5 10 15 20 25 30" indicates that the keys of the AVL tree, when traversed in inorder, are sorted as 5, 10, 15, 20, 25, and 30.
- This confirms that the AVL tree maintains its balanced property and correctly orders its keys.