Back to Data Structures & Algorithms
2026-01-265 min read

Balanced Binary Tree (Data Structures & Algorithms)

Learn Balanced Binary Tree (Data Structures & Algorithms) step by step with clear examples and exercises.

Why This Matters

Welcome to the full guide on Balanced Binary Trees! This lesson is crucial for mastering data structures and algorithms, as it equips you with essential skills needed for coding interviews, real-world programming scenarios, and even debugging common issues in your own code. Let's look at deeper into this fascinating topic!

Prerequisites

Before we dive into the core concept, it is essential to have a solid understanding of:

  1. Basic Python syntax and data structures (lists, dictionaries)
  2. Recursion concepts
  3. Tree data structure basics
  4. Understanding of Big O notation for time complexity analysis

Core Concept

A Balanced Binary Tree is a binary tree where the difference between the heights of left and right subtrees for any node never exceeds 1. This property ensures that the tree remains balanced, which in turn leads to optimal performance during various operations such as insertion, deletion, and searching.

AVL Trees

One common implementation of a Balanced Binary Tree is the AVL (Adelson-Velsky and Landis) tree. It's a self-balancing binary search tree where each node has a balance factor that keeps track of the height difference between its left and right subtrees.

class Node:
def __init__(self, key):
self.left = None
self.right = None
self.val = key
self.height = 1

class AVLTree:
def __init__(self):
self.root = None

Rotations

To maintain balance, AVL trees perform rotations (single and double) around the nodes. These operations help rearrange the tree structure while preserving its sorted order.

def left_rotate(self, z):
y = z.right
T2 = y.left
y.left = z
z.right = T2
y.height = max(height(y.left), height(y.right)) + 1
z.height = max(height(z.left), height(z.right)) + 1
return y

def right_rotate(self, x):
y = x.left
T2 = y.right
y.right = x
x.left = T2
y.height = max(height(y.left), height(y.right)) + 1
x.height = max(height(x.left), height(x.right)) + 1
return y

Balance Factor and Height Updates

After rotations, the balance factor of affected nodes is updated to ensure that the tree remains balanced.

def height(node):
if node is None:
return 0
return node.height

def get_balance_factor(self, node):
if node is None:
return 0
return height(node.right) - height(node.left)

Insertion and Deletion

Inserting or deleting a node may cause an imbalance in the tree. To maintain balance, we perform various adjustments, including rotations and rebalancing operations.

def insert(self, root, key):
if root is None:
return Node(key)

if key < root.val:
root.left = self.insert(root.left, key)
self.balance(root)
else:
root.right = self.insert(root.right, key)
self.balance(root)

return root

def delete_node(self, root, key):
if root is None:
return root

if key < root.val:
root.left = self.delete_node(root.left, key)
self.balance(root)
elif key > root.val:
root.right = self.delete_node(root.right, key)
self.balance(root)
else:
if root.left is None:
return root.right
elif root.right is None:
return root.left

temp = self.right_rotate(self, root.left)
root.left = temp.left
temp.left = root
return self.balance(temp)

Balancing Operations

The balance() function performs various adjustments to maintain the balance of the tree after insertion or deletion.

def balance(self, node):
hl = height(node.left)
hr = height(node.right)

if hr - hl > 1:
if hr - hl == 2 and height(node.right.left) >= height(node.right.right):
return self.rotate_left(node)
elif hr - hl == 2 and height(node.right.left) < height(node.right.right):
node.right = self.rotate_right(node.right)
return self.rotate_left(node)
else:
temp = self.right_rotate(node.right)
node.right = temp.left
temp.left = node
return self.balance(temp)

if hl - hr > 1:
if hl - hr == 2 and height(node.left.right) >= height(node.left.left):
return self.rotate_right(node)
elif hl - hr == 2 and height(node.left.right) < height(node.left.left):
node.left = self.rotate_left(node.left)
return self.rotate_right(node)
else:
temp = self.left_rotate(node.left)
node.left = temp.right
temp.right = node
return self.balance(temp)

Worked Example

Let's construct an AVL tree and perform various operations to see how the tree maintains its balance.

avl = AVLTree()
avl.root = avl.insert(avl.root, 10)
avl.root = avl.insert(avl.root, 20)
avl.root = avl.insert(avl.root, 30)
avl.root = avl.insert(avl.root, 40)
avl.root = avl.insert(avl.root, 50)
avl.root = avl.insert(avl.root, 25)

print("Original AVL Tree:")
self._print_tree(avl.root)

Inserting a node that should cause an imbalance

avl.root = avl.insert(avl.root, 22)

print("\nAfter inserting 22:")

self._print_tree(avl.root)

Common Mistakes

  1. Forgetting to update the height after rotations or rebalancing operations.
  2. Neglecting to check for the case where a node has no children (leaf node) during deletion.
  3. Improperly handling the balance factor updates after rotations and rebalancing operations.
  4. Incorrectly determining which rotation is needed based on the balance factor differences.
  5. Failing to handle duplicate keys correctly during insertion and deletion.
  6. Overlooking edge cases, such as empty trees or trees with a single node, when performing tree operations.

Practice Questions

  1. Implement _print_tree() to print the AVL tree in a readable format.
  2. Write a function to find the height of an AVL tree.
  3. Implement a function to search for a key in an AVL tree.
  4. Modify the insert and delete functions to handle duplicate keys correctly.
  5. Analyze the time complexity of various operations in AVL trees using Big O notation.
  6. Compare AVL trees with other self-balancing binary search trees like Red-Black Trees, and discuss their advantages and disadvantages.

FAQ

  1. Why is maintaining balance important in AVL trees? Balancing ensures that the tree remains efficient during various operations, as the height of the tree directly affects the number of comparisons needed for searching.
  2. What happens when an AVL tree becomes unbalanced? When a node's balance factor exceeds 1 or goes below -1, it indicates that the tree is unbalanced. Rotations and rebalancing operations are then performed to restore balance.
  3. Can AVL trees handle deletions efficiently? Yes, AVL trees can handle deletions efficiently due to their self-balancing property. However, they may require more memory than other balanced binary search trees like Red-Black Trees.
  4. How does the height of an AVL tree affect its performance? The height of an AVL tree directly impacts the time complexity of various operations. For example, searching for a node in an AVL tree with a height of h would take O(h) time on average.
  5. What are some alternative self-balancing binary search trees? Other common self-balancing binary search trees include Red-Black Trees, Splay Trees, and Treap (Priority Stack Tree). Each tree has its advantages and disadvantages in terms of performance, memory usage, and implementation complexity.
Balanced Binary Tree (Data Structures & Algorithms) | Data Structures & Algorithms | XQA Learn