AVL Tree¶
An AVL Tree (named after inventors Adelson-Velsky and Landis) is a self-balancing Binary Search Tree that maintains height balance through automatic rotations.
Overview¶
The AVLTree implementation extends BinarySearchTree and guarantees O(log n) time complexity for all operations by maintaining the AVL property: the heights of the two child subtrees of any node differ by at most 1.
Properties¶
- AVL Property: For every node,
|height(left) - height(right)| ≤ 1 - Balance Factor:
balance_factor = height(left) - height(right) - Automatic Rebalancing: Tree rebalances after insertions and deletions
- Guaranteed Performance: O(log n) for all operations
Operations¶
Creating an AVL Tree¶
from py_ds import AVLTree
# Create an empty AVL tree
avl = AVLTree()
# Create an AVL tree from an iterable
avl = AVLTree([1, 2, 3, 4, 5, 6, 7])
# Print the tree structure (visual representation)
print(avl)
# Output:
# ┌── 7
# ┌── 6
# │ └── 5
# 4
# │ ┌── 3
# └── 2
# └── 1
Insertion¶
Deletion¶
Search¶
All BST Operations¶
All BinarySearchTree operations are available:
# Min/Max
min_val = avl.min() # O(log n)
max_val = avl.max() # O(log n)
# Traversals
inorder = list(avl.inorder())
preorder = list(avl.preorder())
postorder = list(avl.postorder())
level_order = list(avl.level_order())
Rotations¶
AVL trees use rotations to maintain balance:
Left Rotation¶
Used when right subtree is taller:
Right Rotation¶
Used when left subtree is taller:
Left-Right Rotation¶
Used for left-right imbalance:
Right-Left Rotation¶
Used for right-left imbalance:
Time Complexity¶
| Operation | Time Complexity |
|---|---|
insert(item) |
O(log n) |
remove(item) |
O(log n) |
contains(item) |
O(log n) |
min() |
O(log n) |
max() |
O(log n) |
| Traversals | O(n) |
All operations are guaranteed O(log n) - no worst-case degradation to O(n).
Space Complexity¶
O(n) where n is the number of nodes.
Use Cases¶
- When Guaranteed Performance Matters - Critical applications requiring O(log n)
- Real-Time Systems - Predictable performance characteristics
- Database Indexing - When balanced trees are required
- Priority Queues - Combined with heap operations
- Range Queries - Efficient range searches on sorted data
Example: Maintaining Sorted Order¶
avl = AVLTree([5, 3, 7, 2, 4, 6, 8])
# Tree remains balanced after all operations
avl.insert(1) # Automatically rebalances
avl.insert(9) # Automatically rebalances
avl.remove(5) # Automatically rebalances
# Print the tree structure to see the balanced tree
print(avl)
# Height is kept minimal
print(avl.height) # O(log n) height guaranteed
# All operations remain O(log n)
sorted_values = list(avl.inorder()) # Always sorted
Example: Comparison with BST¶
from py_ds import BinarySearchTree, AVLTree
# Worst case: inserting sorted sequence
values = list(range(1000))
# BST becomes a linked list (O(n) operations)
bst = BinarySearchTree(values)
print(bst.height) # ~1000 (unbalanced!)
# AVL stays balanced (O(log n) operations)
avl = AVLTree(values)
print(avl.height) # ~10 (balanced!)
Balance Factor¶
Each node maintains a balance factor:
balance_factor = height(left) - height(right)- Valid values: -1, 0, or 1
- If balance factor is outside this range, rotation occurs
When to Use AVL vs BST¶
Use AVLTree when:
- You need guaranteed O(log n) performance
- Data insertion order is unpredictable
- Worst-case performance matters
- You're inserting/deleting frequently
Use BinarySearchTree when:
- Data is inserted in random order
- Worst-case performance is acceptable
- Simplicity is preferred
- Memory overhead is a concern