Unit 25: Balanced Trees
Keeping a tree from leaning.
Unit 25 of 31 in Python for kids. Its 4 lessons are When a Tree Falls Over, Straightening It Out, Fixing As You Go and Balance Master — below is everything each one explains, and a question or two from it to try.
Every sample on this page was run through real Python before it shipped, and prints exactly what it says it prints.
This unit opens with a fortnight’s trial of everything — no card needed — or with a family plan, bought in the iPhone app. The first two units of every track are free for ever. Try it in the app.
📉 When a Tree Falls Over
Sorted input ruins a search tree
Insert numbers already in order and every one goes right. The tree becomes a straight line — a linked list wearing a tree costume — and the fast search is gone.
Python
class Node:
def __init__(self, v):
self.value = v
self.left = None
self.right = None
def insert(node, value):
if node is None:
return Node(value)
if value < node.value:
node.left = insert(node.left, value)
else:
node.right = insert(node.right, value)
return node
def height(node):
if node is None:
return 0
return 1 + max(height(node.left), height(node.right))
leaning = None
for n in [1, 2, 3, 4, 5]:
leaning = insert(leaning, n)
balanced = None
for n in [3, 2, 4, 1, 5]:
balanced = insert(balanced, n)
print(height(leaning))
print(height(balanced))
It prints
5 3
Height is the thing that matters
A search never takes more steps than the tree is tall. Keeping the tree short *is* keeping it fast — so the whole job is stopping it from leaning.
Python
print("10 values, leaning : up to 10 steps")
print("10 values, balanced: up to 4 steps")
print("1000 values, balanced: up to 10 steps")
It prints
10 values, leaning : up to 10 steps 10 values, balanced: up to 4 steps 1000 values, balanced: up to 10 steps
Measuring the lean
The balance factor of a node is the height of its left side take away the height of its right. 0 means even, and anything beyond -1 or 1 means that node has tipped too far.
Python
class Node:
def __init__(self, v):
self.value = v
self.left = None
self.right = None
def height(node):
if node is None:
return 0
return 1 + max(height(node.left), height(node.right))
def balance(node):
return height(node.left) - height(node.right)
root = Node(3)
root.left = Node(2)
root.left.left = Node(1)
print(balance(root))
print(balance(root.left))
It prints
2 1
Try it yourself
Why is a tall thin tree slow?
- It uses more memory
- A search has to walk down nearly every node, instead of throwing half away each step
- It cannot be sorted
- It is not slow
A node has a balance factor of 2. What does that mean?
- It has two children
- Its left side is two taller than its right — it is leaning left too far
- It holds the value 2
- It is perfectly balanced
Answer them in the app
🔄 Straightening It Out
Lifting the middle one up
Three nodes in a line can be re-hung so the middle becomes the top. Nothing is added or removed — only three pointers change, and the BST rule still holds.
Python
class Node:
def __init__(self, v):
self.value = v
self.left = None
self.right = None
# 3 -> 2 -> 1 leaning left
root = Node(3)
root.left = Node(2)
root.left.left = Node(1)
new_root = root.left
root.left = new_root.right
new_root.right = root
print(new_root.value)
print(new_root.left.value, new_root.right.value)
It prints
2 1 3
That is a rotation
Those three lines are a right rotation, and they are the whole trick. Written as a function it works on any node that is leaning left.
Python
class Node:
def __init__(self, v):
self.value = v
self.left = None
self.right = None
def rotate_right(node):
new_root = node.left
node.left = new_root.right
new_root.right = node
return new_root
root = Node(5)
root.left = Node(3)
root.left.left = Node(1)
root = rotate_right(root)
print(root.value, root.left.value, root.right.value)
It prints
3 1 5
And the mirror image
A left rotation is exactly the same with left and right swapped, for a node leaning right.
Python
class Node:
def __init__(self, v):
self.value = v
self.left = None
self.right = None
def rotate_left(node):
new_root = node.right
node.right = new_root.left
new_root.left = node
return new_root
root = Node(1)
root.right = Node(3)
root.right.right = Node(5)
root = rotate_left(root)
print(root.value, root.left.value, root.right.value)
It prints
3 1 5
Try it yourself
What does a rotation change?
- The values in the tree
- Only which node points at which — the values and their order stay the same
- The number of nodes
- Nothing at all
What does this print?
Python
class Node:
def __init__(self, v):
self.value = v
self.left = None
self.right = None
def rotate_right(node):
new_root = node.left
node.left = new_root.right
new_root.right = node
return new_root
r = Node(10)
r.left = Node(5)
r.left.left = Node(2)
r = rotate_right(r)
print(r.value)
Answer them in the app
🩹 Fixing As You Go
An AVL tree checks itself after every insert
An AVL tree is a BST that, every time you add something, looks at the balance on the way back up and rotates whenever a node has tipped past 1.
Python
print("insert -> walk back up -> check balance -> rotate if needed")
It prints
insert -> walk back up -> check balance -> rotate if needed
The simple lean: one rotation
When a node leans left and its left child also leans left, one right rotation fixes it. This is the "left-left" case.
Python
class Node:
def __init__(self, v):
self.value = v
self.left = None
self.right = None
def height(n):
return 0 if n is None else 1 + max(height(n.left), height(n.right))
def rotate_right(node):
new_root = node.left
node.left = new_root.right
new_root.right = node
return new_root
root = Node(3)
root.left = Node(2)
root.left.left = Node(1)
print(height(root))
root = rotate_right(root)
print(height(root))
It prints
3 2
The awkward lean: two rotations
When a node leans left but its child leans *right*, one rotation is not enough. Rotate the child left first to make it a simple lean, then rotate the node right. This is "left-right".
Python
class Node:
def __init__(self, v):
self.value = v
self.left = None
self.right = None
def rotate_left(node):
r = node.right
node.right = r.left
r.left = node
return r
def rotate_right(node):
l = node.left
node.left = l.right
l.right = node
return l
# 3 leans left, its child 1 leans right
root = Node(3)
root.left = Node(1)
root.left.right = Node(2)
root.left = rotate_left(root.left)
root = rotate_right(root)
print(root.value, root.left.value, root.right.value)
It prints
2 1 3
Try it yourself
A node leans left, and its left child leans right. What is needed?
- One right rotation
- Rotate the child left, then the node right
- One left rotation
- Nothing
Answer it in the app
🏆 Balance Master
Try it yourself
What does this print?
Python
class Node:
def __init__(self, v):
self.value = v
self.left = None
self.right = None
def height(n):
return 0 if n is None else 1 + max(height(n.left), height(n.right))
r = Node(5)
r.left = Node(3)
r.left.left = Node(1)
print(height(r.left) - height(r.right))
Answer it in the app