Released · improving
Trees and heaps guide · 2/6
This chapter traces small examples by hand: tree traversals, BST insert, search and delete, and the heap operations sift up, sift down and heapify. The example tree is the BST you get by inserting 50, 30, 70, 20, 40, 60, 80 in that order.
50
/ \
30 70
/ \ / \
20 40 60 80A traversal visits every node once. The three depth-first traversals differ only in when the node itself is visited.
| Traversal | Order | Result on the example |
|---|---|---|
| Pre-order | node, left, right | 50 30 20 40 70 60 80 |
| In-order | left, node, right | 20 30 40 50 60 70 80 |
| Post-order | left, right, node | 20 40 30 60 80 70 50 |
| Level-order | by depth, left to right | 50 30 70 20 40 60 80 |
Use pre-order to copy or serialize a tree, in-order to read a BST in sorted order, and post-order when a parent's value depends on its children (folder sizes, freeing a tree). Level-order is breadth-first search with a queue.
from collections import deque
class Node:
def __init__(self, key):
self.key, self.left, self.right = key, None, None
def insert(root, key):
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)
return root
def preorder(n):
return [n.key] + preorder(n.left) + preorder(n.right) if n else []
def inorder(n):
return inorder(n.left) + [n.key] + inorder(n.right) if n else []
def postorder(n):
return postorder(n.left) + postorder(n.right) + [n.key] if n else []
def level_order(root):
out, queue = [], deque([root] if root else [])
while queue:
n = queue.popleft()
out.append(n.key)
if n.left:
queue.append(n.left)
if n.right:
queue.append(n.right)
return out
root = None
for k in [50, 30, 70, 20, 40, 60, 80]:
root = insert(root, k)
print(preorder(root), inorder(root), postorder(root), level_order(root), sep="\n")To find 60, compare with 50 (larger, go right to 70), then with 70 (smaller, go left to 60), and you are done after three comparisons. Searching for 65 stops at the empty right child of 60 and reports "not found". Insertion attaches the new node at exactly that empty spot, so inserting 65 makes it the right child of 60.
| Step | Current node | Comparison | Next |
|---|---|---|---|
| 1 | 50 | 65 is larger | right, 70 |
| 2 | 70 | 65 is smaller | left, 60 |
| 3 | 60 | 65 is larger | right is empty, insert here |
Deletion depends on how many children the node has.
def delete(root, key):
if root is None:
return None
if key < root.key:
root.left = delete(root.left, key)
elif key > root.key:
root.right = delete(root.right, key)
else:
if root.left is None:
return root.right
if root.right is None:
return root.left
succ = root.right
while succ.left:
succ = succ.left
root.key = succ.key
root.right = delete(root.right, succ.key)
return root
root = delete(root, 30)
print(inorder(root), preorder(root))
# [20, 40, 50, 60, 70, 80] [50, 40, 20, 70, 60, 80]Insert 0 into the min-heap [1, 3, 2, 7, 4, 5]. To keep the complete shape, append it at the end (index 6), then swap it with its parent while it is smaller.
| Step | Array | Note |
|---|---|---|
| 0 | 1 3 2 7 4 5 0 | append 0 at index 6, parent index 2 |
| 1 | 1 3 0 7 4 5 2 | swap 0 and 2, now index 2, parent index 0 |
| 2 | 0 3 1 7 4 5 2 | swap 0 and 1, reached the root |
Remove the root (0), move the last element (2) to the root, then swap it with its smaller child until it settles.
| Step | Array | Note |
|---|---|---|
| 0 | 2 3 1 7 4 5 | last element 2 moved to the root |
| 1 | 1 3 2 7 4 5 | children are 3 and 1, swap with 1 |
| 2 | 1 3 2 7 4 5 | index 2 has only child 5, larger than 2, stop |
Pushing n items one by one costs O(n log n). Running sift down from the last internal node (index n // 2 - 1) back to the root builds a heap in O(n), because most nodes sit near the bottom and only move a little.
def sift_down(a, i, n):
while True:
small, l, r = i, 2 * i + 1, 2 * i + 2
if l < n and a[l] < a[small]:
small = l
if r < n and a[r] < a[small]:
small = r
if small == i:
return
a[i], a[small] = a[small], a[i]
i = small
def heapify(a):
for i in range(len(a) // 2 - 1, -1, -1):
sift_down(a, i, len(a))
print(f"i={i}: {a}")
heapify([9, 5, 7, 1, 3, 2])
# i=2: [9, 5, 2, 1, 3, 7]
# i=1: [9, 1, 2, 5, 3, 7]
# i=0: [1, 3, 2, 5, 9, 7]The three depth-first traversals differ only in when the node is visited, and level-order uses a queue. A BST search or insert walks down one side per comparison, and a node with two children is deleted by replacing it with its in-order successor. A heap stays valid with just two moves, sift up after appending and sift down after replacing the root, and heapify runs sift down bottom-up to build a heap in O(n).
0 comments
Sign in · Sign in to leave a comment.
Be the first to comment.