AVL Tree (Data Structures & Algorithms)
Learn AVL Tree (Data Structures & Algorithms) step by step with clear examples and exercises.
Title: AVL Tree (Data Structures & Algorithms) - Python Implementation and Practical Examples
Why This Matters
AVL trees are essential data structures in computer science for efficient management of data, particularly in applications requiring frequent insertions, deletions, or lookups. They ensure the height of the tree remains balanced, reducing the overall complexity of operations compared to regular binary search trees (BST). In this lesson, we will explore AVL trees, understand their properties, and learn how to implement them using Python. We'll also delve into practical examples, common mistakes, and practice questions to help you master AVL trees for exams or real-world projects.
Prerequisites
To follow this lesson, you should be familiar with the following concepts:
- Basic Python programming (variables, functions, loops, and conditional statements)
- Data structures like lists and dictionaries
- Recursion
- Binary search trees (BST) and their basic operations (insertion, deletion, and lookup)
- Understanding of tree traversal algorithms (inorder, preorder, postorder)
- Familiarity with Python's built-in functions like
min(),max(), andlen() - Basic knowledge of Python classes and objects
- Understanding of the Big O notation for time complexity analysis
- Knowledge of basic tree terminology (root, leaf, parent, child, sibling)
Core Concept
Definition and Properties of AVL Trees
An AVL tree is a self-balancing binary search tree named after its inventors Adelson-Velsky and Landis. It consists of nodes that store data and an additional field called the balance factor, which represents the difference between the heights of its left and right subtrees.
- A new node is initially balanced (balance factor = 0)
- After insertion or deletion, if a node's balance factor becomes -1 or 1, it remains balanced. However, if it becomes -2 or 2, the tree must be rebalanced.
Balancing Operations
There are four basic balancing operations in AVL trees:
- Single Rotation (Left or Right): Adjusts the height of a single subtree to maintain balance.
- Double Rotation (Left-Right or Right-Left): Corrects a tree where both subtrees are imbalanced.
- Right-Left with Long Left Subtree: First performs a left rotation on the long left subtree, then a right rotation on the entire subtree. This operation is also known as a "left-leaning triple rotation."
- Left-Right with Long Right Subtree: First performs a right rotation on the long right subtree, then a left rotation on the entire subtree. This operation is also known as a "right-leaning triple rotation."
Single Rotation Example
Let's consider a simple example of a single rotation (left rotation) in an AVL tree:
class Node:
... (existing code)
class AVLTree:
def left_rotate(self, node):
right = node.right
new_root = right.left
right.left = node
node.right = new_root.right
new_root.right = node
Update balance factors and heights
self._update_height(node)
self._update_balance_factor(new_root)
In the above example, `left_rotate` is a function that performs a left rotation on the given node. The new root becomes the right subtree of the original node, and the original node becomes the left subtree of the new root. Balance factors and heights are updated accordingly.
### Double Rotation Example
Double rotations involve performing two single rotations to rebalance the tree. Here's an example of a double rotation (left-right):
class AVLTree:
def left_right_rotate(self, node):
self.left_rotate(node.left)
self.right_rotate(node)
... (existing code)
In the above example, `left_right_rotate` is a function that performs a double rotation on the given node by first performing a left rotation on its left subtree and then a right rotation on the entire subtree.
### Balance Factor Update Rules
1. After inserting a new node as a leaf, update the balance factors of its ancestors until reaching the root. If any node has a balance factor greater than 1 or less than -1, rebalance the tree accordingly.
2. After deleting a node, update the balance factors of its ancestors until reaching the root. If any node has a balance factor greater than 1 or less than -1, rebalance the tree accordingly.
Worked Example
Let's implement an AVL tree in Python and perform insertion, deletion, and lookup operations. We will also create functions to check the tree's validity and find its height.
class Node:
... (existing code)
class AVLTree:
def __init__(self):
self.root = None
def _height(self, node):
if not node:
return 0
return max(self._height(node.left), self._height(node.right)) + 1
def _balance_factor(self, node):
if not node:
return 0
return self._height(node.left) - self._height(node.right)
def insert(self, key, value):
... (existing code for BST insertion)
Update balance factor and height after insertion
self._update_balance_factor(node)
self._update_height(node)
if self._balance_factor(node) == 2 or self._balance_factor(node) == -2:
Perform necessary rotations to rebalance the tree
self._rebalance(node)
def delete(self, key):
... (existing code for BST deletion)
Update balance factor and height after deletion
self._update_balance_factor(node)
self._update_height(node)
if self._balance_factor(node) == -2 or self._balance_factor(node) == 2:
Perform necessary rotations to rebalance the tree
self._rebalance(node)
def lookup(self, key):
... (existing code for BST lookup)
def is_valid(self):
Check all nodes have a balance factor between -1 and 1
return self._is_valid_helper(self.root)
def height(self):
return self._height(self.root)
def _is_valid_helper(self, node):
if not node:
return True
left_valid = self._is_valid_helper(node.left)
right_valid = self._is_valid_helper(node.right)
if left_valid and right_valid and -1 <= self._balance_factor(node) <= 1:
return True
else:
print("Invalid AVL tree")
return False
Common Mistakes
- ### Forgetting to update the balance factor after a rotation
When performing rotations, remember to update the balance factors of both the parent and rotated node.
- ### Incorrect height calculation
Ensure that you calculate the height of each node correctly by considering the maximum height of its subtrees plus 1.
- ### Not rebalancing when necessary
If a node's balance factor becomes -2 or 2 after an operation, make sure to rebalance the tree accordingly.
- ### Incorrect implementation of balancing operations
Ensure that you correctly implement the four basic balancing operations (single rotation, double rotation, right-leaning triple rotation, left-leaning triple rotation) to maintain the AVL property.
- ### Inefficient lookup, insertion, or deletion functions
Optimize your lookup, insertion, and deletion functions by using recursion and returning early when possible.
- ### Not handling cases where multiple rotations are required
In some situations, multiple rotations may be necessary to rebalance the tree. Ensure that you handle these cases correctly.
- ### Incorrect implementation of balance factor update rules
Ensure that you correctly implement the balance factor update rules for insertion and deletion operations.
Practice Questions
- Implement the remaining AVL tree functions (insertion, deletion, lookup) and test them with various examples.
- Write a function to find the height of an AVL tree.
- Given an unbalanced binary search tree, write a Python function to convert it into an AVL tree.
- Write a function to print the contents of an AVL tree in inorder traversal.
- What is the time complexity of insertion and deletion in AVL trees?
- How does an AVL tree ensure balance during operations?
- Implement the four basic balancing operations (single rotation, double rotation, right-leaning triple rotation, left-leaning triple rotation) using Python functions.
- Write a function to find the minimum and maximum keys in an AVL tree.
- Given two sorted lists, write a Python function to merge them into an AVL tree.
- Write a function to check if an AVL tree is valid (i.e., all nodes have a balance factor between -1 and 1).
- Implement a function to perform depth-first search (DFS) on an AVL tree.
- Write a function to find the kth smallest element in an AVL tree.
- Given two AVL trees, write a Python function to merge them into a single AVL tree.
- Implement a function to find the common ancestors of two nodes in an AVL tree.
- Write a Python function to find the height of the tallest subtree in an AVL tree.
FAQ
- What is the main difference between AVL trees and regular binary search trees?
AVL trees are self-balancing, ensuring that the height of the tree remains balanced during insertions and deletions. This reduces the overall complexity of operations compared to regular binary search trees (BST).
- What is the time complexity of lookup, insertion, and deletion in AVL trees?
The average time complexity for lookup, insertion, and deletion in AVL trees is O(log n), while the worst-case scenario is O(log n) as well.
- How does an AVL tree maintain balance during operations?
An AVL tree maintains balance by performing rotations (single or double) and triple rotations when necessary to adjust the height of imbalanced subtrees. This ensures that the height difference between the left and right subtrees remains within a specific range, typically -1 and 1.
- What are the four basic balancing operations in AVL trees?
The four basic balancing operations in AVL trees are single rotation (left or right), double rotation (left-right or right-left), and left-leaning triple rotation, right-leaning triple rotation.
- Why do we need to update balance factors after a rotation?
Updating the balance factors after a rotation ensures that the tree remains balanced. The new balance factor of a node affects its ancestors' balance factors, which may require further rotations to maintain the AVL property.
- What is the height of an empty AVL tree?
An empty AVL tree has a height of 0.