Python Tutorial
Binary Trees
A tree where every node has at most two children — the basis for search trees, heaps, and expression trees.
Definition and Types
In a binary tree each node has a left child, a right child, or neither. Common shapes:
- Full: every node has 0 or 2 children.
- Complete: all levels filled except possibly the last, which fills left to right (heaps).
- Perfect: all leaves at the same depth.
- Balanced: left and right heights differ by at most a small constant, keeping operations O(log n).
The Node
class Node:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
# 1
# / \
# 2 3
# / \
# 4 5
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)The Three Depth-First Traversals
They differ only in when you visit the current node relative to its subtrees.
def preorder(n): # node, left, right
if n:
print(n.value, end=" ")
preorder(n.left)
preorder(n.right)
def inorder(n): # left, node, right
if n:
inorder(n.left)
print(n.value, end=" ")
inorder(n.right)
def postorder(n): # left, right, node
if n:
postorder(n.left)
postorder(n.right)
print(n.value, end=" ")
preorder(root) # 1 2 4 5 3
inorder(root) # 4 2 5 1 3
postorder(root) # 4 5 2 3 1In-order traversal of a binary search tree visits values in sorted order — a key property used everywhere.
Breadth-First (Level Order)
from collections import deque
def level_order(root):
if not root:
return
q = deque([root])
while q:
node = q.popleft()
print(node.value, end=" ")
if node.left: q.append(node.left)
if node.right: q.append(node.right)
level_order(root) # 1 2 3 4 5Height and Node Count
def height(n):
if n is None:
return -1 # edges; use 0 to count nodes
return 1 + max(height(n.left), height(n.right))
def count(n):
if n is None:
return 0
return 1 + count(n.left) + count(n.right)
print(height(root)) # 2
print(count(root)) # 5A binary tree with n nodes has height between log₂(n) (balanced) and n−1 (a degenerate chain). Balance is what keeps search trees fast.
Best Practices
- Pick the traversal that matches your goal: pre-order to copy, in-order for sorted output, post-order to delete/evaluate.
- Use level-order (BFS) for shortest-path-by-edges and level-based problems.
- Keep trees balanced (BST → AVL / Red-Black) to guarantee O(log n) operations.
- Store complete binary trees (heaps) in a plain array: children of index i are 2i+1 and 2i+2.
Try It Yourself
Exercise 1: Write a recursive function that counts leaf nodes in a binary tree.
Show solution
def leaves(n):
if n is None:
return 0
if n.left is None and n.right is None:
return 1
return leaves(n.left) + leaves(n.right)Exercise 2: Which traversal of a BST yields values in sorted order?
Show solution
In-order (left, node, right).
📘 Real-World Deep Dive
Knowing <strong>DSA Binary Trees (algorithms & data structures)</strong> well is what turns algorithms & data structures from a curiosity into a daily tool — you'll reach for it in nearly every real project.
Real-Life Scenario
An end-to-end usage of DSA Binary Trees that you'd actually see in a data pipeline or analytics notebook.
Real-Life Example
class TreeNode:
__slots__ = ("val","left","right")
def __init__(self, val, left=None, right=None):
self.val=val; self.left=left; self.right=right
def inorder(root):
return inorder(root.left) + [root.val] + inorder(root.right) if root else []
t = TreeNode(4, TreeNode(2, TreeNode(1), TreeNode(3)),
TreeNode(6, TreeNode(5), TreeNode(7)))
print(inorder(t))Expected Output
(see source)Common mistakes
- Off-by-one errors in binary-search: the standard idiom is
while lo <= hiwithmid = (lo + hi) // 2. - Recursive algorithms blow the stack for n > ~10⁴; convert to iterative with an explicit stack.
- Comparison algorithms (
sorted(iterable)) are stable by default in Python — surprising for Java/C++ users. - Treating DSA Binary Trees as a black box without reading the docs — the API has subtle defaults that bite when you scale.
🚀 Performance & Best Practices
- Use
bisect.bisect_left/bisect_rightinstead of writing your own binary search. - Convert a sorted search into a tuple-access pattern with
numpy.searchsortedfor huge arrays. - Use
heapqfor priority queues instead of maintaining a sorted list manually. - When working with algorithms & data structures, prefer vectorised / batched operations over Python loops.
🧪 Try It Yourself
- Reproduce the snippet on a representative slice of your own data.
- Profile the snippet with
cProfileortimeitand find the single biggest improvement. - Generalise the snippet into a small, reusable function you can drop into future projects.