Trees — Binary Trees, Traversals, BSTs and Heaps
Tree terminology, binary tree properties and counting results, array and linked representations, pre-, in-, post- and level-order traversals and reconstructing a tree from two traversals, binary search trees (search, insertion, deletion, balance), binary heaps and priority queues (sift-up, sift-down, build-heap in O(n), heapsort, Python's heapq), and expression trees.
Terminology
A tree is a connected acyclic graph; a rooted tree designates one node as the root.
| Term | Meaning |
|---|---|
| parent, child, sibling | adjacent nodes one level apart / sharing a parent |
| leaf (external node) | node with no children |
| internal node | node with at least one child |
| depth of a node | edges from the root to it |
| height of a node | edges on the longest path down to a leaf; tree height = root's height |
| level | depth (root is level 0) |
| subtree | a node and all its descendants |
| degree | number of children |
Binary trees
Each node has at most two children, left and right.
| Type | Definition |
|---|---|
| Full (proper) | every node has 0 or 2 children |
| Complete | all levels full except possibly the last, which is filled left to right |
| Perfect | all internal nodes have 2 children and all leaves are at the same depth |
| Skewed | every node has at most one child (degenerates to a linked list) |
| Balanced | heights of left and right subtrees differ by at most 1 at every node (AVL) |
Counting facts
- At most nodes at level ; at most nodes in a tree of height .
- With nodes, height is between and .
- : leaves exceed degree-2 nodes by one. A full binary tree with internal nodes has leaves and nodes.
- A binary tree with nodes has edges and
Nonechild pointers. - Number of distinct binary tree shapes with nodes: Catalan — 1, 2, 5, 14, 42.
Representations
- Linked:
Node(val, left, right). - Array (ideal for complete trees, e.g. heaps): with 0-based indexing, children of are at and , and the parent at .
Traversals
class Node:
def __init__(self, val, left=None, right=None):
self.val, self.left, self.right = val, left, right
def preorder(t):
return [t.val] + preorder(t.left) + preorder(t.right) if t else []
def inorder(t):
return inorder(t.left) + [t.val] + inorder(t.right) if t else []
def postorder(t):
return postorder(t.left) + postorder(t.right) + [t.val] if t else []
from collections import deque
def levelorder(t):
out, q = [], deque([t] if t else [])
while q:
node = q.popleft()
out.append(node.val)
q.extend(c for c in (node.left, node.right) if c)
return out
Reconstruction: in-order together with pre-order (or post-order) determines a binary tree uniquely when keys are distinct — the first (or last) element is the root, which splits the in-order sequence into the left and right subtrees. Pre-order plus post-order alone does not determine a general binary tree (it does for full binary trees).
Binary search trees
BST property: for every node, all keys in the left subtree are smaller and all keys in the right subtree are larger.
def search(t, key):
while t and t.val != key:
t = t.left if key < t.val else t.right
return t
def insert(t, key):
if t is None:
return Node(key)
if key < t.val:
t.left = insert(t.left, key)
elif key > t.val:
t.right = insert(t.right, key)
return t
- Search, insert and delete cost : when balanced, in the worst case (inserting sorted keys builds a skewed "linked list").
- Delete: a leaf is removed; a node with one child is replaced by that child; a node with two children takes its in-order successor's key, and the successor is deleted.
- Minimum = leftmost node; maximum = rightmost node; in-order traversal = sorted keys.
- Self-balancing trees (AVL, red–black) keep with rotations.
Binary heaps and priority queues
A binary min-heap is a complete binary tree in which every parent is its children, so the minimum is at the root. Stored in an array.
| Operation | Method | Cost |
|---|---|---|
| peek min | h[0] | |
| insert | append, then sift up (swap with parent while smaller) | |
| extract min | move last element to the root, sift down (swap with the smaller child) | |
| build-heap | sift down every internal node from the last to the root | |
| heapsort | build heap, then extract times | , in place, not stable |
import heapq
h = [9, 4, 7, 1, 8]
heapq.heapify(h) # O(n) → [1, 4, 7, 9, 8]
heapq.heappush(h, 3) # O(log n)
smallest = heapq.heappop(h) # 1
top3 = heapq.nsmallest(3, [5, 1, 8, 3, 9]) # [1, 3, 5]
# max-heap: push negated keys
Priority queues drive Dijkstra's algorithm (see graph algorithms), event simulation, top- selection and scheduling.
Expression trees
Internal nodes are operators and leaves are operands. Pre-, in- and post-order traversals give prefix, infix and postfix expressions; evaluating the tree is a post-order traversal.
Quick revision
Practice questions
GATE-style questions: 3 one-mark and 7 two-mark. Wrong MCQs cost ⅓ of their marks in the real exam; MSQ and NAT have no negative marking. Attempt each before opening the solution.
The maximum number of nodes in a binary tree of height 4 (where a single root node has height 0) is ______.
A binary tree has 10 leaves. The number of nodes with exactly two children is ______.
A binary tree has in-order traversal D B E A F C G and pre-order traversal A B D E C F G. Its post-order traversal is
The keys 50, 30, 70, 20, 40, 60, 80, 35 are inserted in this order into an initially empty binary search tree. The height of the resulting tree (number of edges on the longest root-to-leaf path) is ______.
Which traversal of a binary search tree visits the keys in sorted (ascending) order?
What does this program print?
import heapq
h = []
for x in [5, 3, 8, 1]:
heapq.heappush(h, x)
print(h)
For a binary min-heap with elements, which statements are true?
One or more options are correct.
The number of structurally distinct binary trees with 3 nodes is ______.
To delete a node with two children from a binary search tree, the standard method replaces its key with
Trees are written as tuples (left, value, right) with None for empty. What does this program print?
T = (((None, 4, None), 2, (None, 5, None)), 1, ((None, 6, None), 3, None))
def f(t):
if t is None:
return 0
l, v, r = t
if l is None and r is None:
return v
return f(l) + f(r)
print(f(T))