A tree is a special form of a connected graph without cycles.
A tree with n nodes has exactly n-1 edges, and there is a unique path between any two nodes.
Applying graph traversal (DFS/BFS) to a tree yields different results depending on the traversal order.
The reason this order is important:
Pre-order: Tree copying, serialization
In-order: Output sorted order in a binary search tree
Post-order: Tree deletion, calculating file system size
Tree vs General Graph
───────────────────────────────────────────────────────
Characteristic Tree General Graph
───────────────────────────────────────────────────────
Cycle None May exist
Connectivity Always connected May be disconnected
Number of edges n-1 0 ~ n(n-1)/2
Root Exists (usually) None
Parent-Child Clear No direction (undirected)
───────────────────────────────────────────────────────
Learning Goals
Understand the structure of a tree (nodes, edges, root, leaf) and implement it in Python.
Implement Preorder/Inorder/Postorder traversal both recursively and iteratively (using a stack).
Implement level-order (BFS) and zigzag traversal using a queue, and explain the use cases of each traversal.
Key Concepts
Tree Node Structure
class TreeNode:
def __init__(self, val: int = 0,
left: 'TreeNode | None' = None,
right: 'TreeNode | None' = None):
self.val = val
self.left = left
self.right = right
Missing None check: If you omit the base case in a recursive function, if node is None: return, an AttributeError occurs
Postorder iterative order confusion: Postorder iterative uses "reverse of preorder" or two stacks — simply changing stack order is wrong
BFS without level separation: To group by level when queue.popleft(), you must first store the current level size using level_size = len(queue)
Confusing height and depth: Height is from node to leaf, depth is from root to node — opposite directions
Recursive overflow in skewed trees: Python default recursion limit is 1000 — for large input, use sys.setrecursionlimit() or iterative method
Summary
All tree traversals are time O(n), space O(h) (level-order is O(w))
Recursion is concise, but the risk of stack overflow exists in deep trees
Iterative (Stack/Queue) method uses an explicit stack to replace recursion
Inorder traversal = sorted output of binary search tree (connected to the next lesson)
Try It Yourself
Try implementing maximum depth of a tree (max depth) using BFS and DFS respectively
Write a function to compare if two trees are the same (using recursion)
Determine if there exists a path whose path sum equals the target
Example files
The runnable files used in this lesson. Copy them into your own project and experiment.
src/01_tree_node.pyPython
#Topic: Tree Node Class and Tree Creation
#Time: O(n) — proportional to the number of nodes n
#Space: O(n) — stores all nodes
from __future__ import annotations
from collections import deque
class TreeNode:
"""Binary tree node."""
def __init__(
self,
val: int = 0,
left: TreeNode | None = None,
right: TreeNode | None = None,
) -> None:
self.val = val
self.left = left
self.right = right
def __repr__(self) -> str:
return f"TreeNode({self.val})"
def build_tree_from_list(values: list[int | None]) -> TreeNode | None:
"""Create a binary tree from a level-order list.
Args:
values: List of node values in level order (None = empty spot)
Returns:
Root node of the tree
Time: O(n)
Space: O(n)"""
if not values or values[0] is None:
return None
root = TreeNode(values[0])
queue: deque[TreeNode] = deque([root])
i = 1
while queue and i < len(values):
node = queue.popleft()
#Left child
if i < len(values) and values[i] is not None:
node.left = TreeNode(values[i])
queue.append(node.left)
i += 1
#Right child
if i < len(values) and values[i] is not None:
node.right = TreeNode(values[i])
queue.append(node.right)
i += 1
return root
def tree_height(root: TreeNode | None) -> int:
"""Height of the tree (maximum number of edges from root to leaf).
Time: O(n)
Space: O(h)"""
if root is None:
return -1 #Empty tree height = -1
return 1 + max(tree_height(root.left), tree_height(root.right))
def count_nodes(root: TreeNode | None) -> int:
"""Number of nodes in the tree.
Time: O(n)
Space: O(h)"""
if root is None:
return 0
return 1 + count_nodes(root.left) + count_nodes(root.right)
def print_tree(root: TreeNode | None, level: int = 0, prefix: str = "Root:") -> None:
"""Print the tree as ASCII art.
Time: O(n)
Space: O(h)"""
if root is None:
return
print(" " * (level * 4) + prefix + str(root.val))
if root.left or root.right:
if root.left:
print_tree(root.left, level + 1, "L── ")
else:
print(" " * ((level + 1) * 4) + "L── (None)")
if root.right:
print_tree(root.right, level + 1, "R── ")
else:
print(" " * ((level + 1) * 4) + "R── (None)")
#── Example Execution ──────────────────────────────────────────────────────────────
if __name__ == "__main__":
# 1
# / \
# 2 3
# / \ / \
# 4 5 6 7
values = [1, 2, 3, 4, 5, 6, 7]
root = build_tree_from_list(values)
print("=== Tree Structure ===")
print_tree(root)
print(f"\nTree Height: {tree_height(root)}")
print(f"Number of Nodes: {count_nodes(root)}")
#Asymmetric Tree Test
print("\n=== Asymmetric Tree ===")
asym = build_tree_from_list([1, 2, 3, None, 4, None, 5])
print_tree(asym)
print(f"Height: {tree_height(asym)}, Number of Nodes: {count_nodes(asym)}")
src/02_recursive_traversal.pyPython
#Topic: Tree Traversal — Recursive Method (Pre-order/In-order/Post-order)
#Time: O(n) — Each node visited exactly once
#Space: O(h) — Recursion call stack depth = Tree height
from __future__ import annotations
import sys
sys.setrecursionlimit(10_000)
from collections import deque
class TreeNode:
def __init__(self, val: int = 0,
left: "TreeNode | None" = None,
right: "TreeNode | None" = None) -> None:
self.val = val
self.left = left
self.right = right
#── Recursive Traversal ──────────────────────────────────────────────────────────────
def preorder(root: TreeNode | None) -> list[int]:
"""Preorder traversal: Current → Left → Right.
Uses: Tree copy, serialization, print
Time: O(n)
Space: O(h)"""
if root is None:
return []
return [root.val] + preorder(root.left) + preorder(root.right)
def inorder(root: TreeNode | None) -> list[int]:
"""Inorder traversal: Left → Current → Right.
Uses: Ascending output in BST, expression tree evaluation
Time: O(n)
Space: O(h)"""
if root is None:
return []
return inorder(root.left) + [root.val] + inorder(root.right)
def postorder(root: TreeNode | None) -> list[int]:
"""Postorder traversal: Left → Right → Current.
Uses: Tree deletion, folder size calculation, expression computation
Time: O(n)
Space: O(h)"""
if root is None:
return []
return postorder(root.left) + postorder(root.right) + [root.val]
#── In-place (passing result list as argument) version — Memory efficient ────────────────
def preorder_inplace(root: TreeNode | None, result: list[int]) -> None:
"""In-place preorder traversal (without creating a new list).
Time: O(n)
Space: O(h)"""
if root is None:
return
result.append(root.val)
preorder_inplace(root.left, result)
preorder_inplace(root.right, result)
def inorder_inplace(root: TreeNode | None, result: list[int]) -> None:
"""In-place inorder traversal."""
if root is None:
return
inorder_inplace(root.left, result)
result.append(root.val)
inorder_inplace(root.right, result)
def postorder_inplace(root: TreeNode | None, result: list[int]) -> None:
"""In-place postorder traversal."""
if root is None:
return
postorder_inplace(root.left, result)
postorder_inplace(root.right, result)
result.append(root.val)
#── Helper: Create tree from list ─────────────────────────────────────────────────────
def build(values: list[int | None]) -> TreeNode | None:
if not values or values[0] is None:
return None
root = TreeNode(values[0])
q: deque[TreeNode] = deque([root])
i = 1
while q and i < len(values):
node = q.popleft()
if i < len(values) and values[i] is not None:
node.left = TreeNode(values[i])
q.append(node.left)
i += 1
if i < len(values) and values[i] is not None:
node.right = TreeNode(values[i])
q.append(node.right)
i += 1
return root
#── Example Execution ──────────────────────────────────────────────────────────────
if __name__ == "__main__":
# 1
# / \
# 2 3
# / \ / \
# 4 5 6 7
root = build([1, 2, 3, 4, 5, 6, 7])
print("=== Recursive Traversal Results ===")
print(f"Pre-order: {preorder(root)}")
print(f"In-order: {inorder(root)}")
print(f"Post-order: {postorder(root)}")
print("=== In-place Traversal ===")
res_pre: list[int] = []
res_in: list[int] = []
res_post: list[int] = []
preorder_inplace(root, res_pre)
inorder_inplace(root, res_in)
postorder_inplace(root, res_post)
print(f"Pre-order: {res_pre}")
print(f"In-order: {res_in}")
print(f"Post-order: {res_post}")
#In BST, In-order = Sorted
print("=== BST In-order Traversal = Sorted ===")
# 4
# / \
# 2 6
# / \ / \
# 1 3 5 7
bst = build([4, 2, 6, 1, 3, 5, 7])
print(f"BST In-order Traversal: {inorder(bst)} ← Ascending!")
src/03_iterative_traversal.pyPython
#Topic: Tree Traversal — Iterative (Stack) Method (Pre-order/In-order/Post-order)
#Time: O(n) — Each node visited exactly once
#Space: O(h) — Explicit stack size = tree height
from __future__ import annotations
from collections import deque
class TreeNode:
def __init__(self, val: int = 0,
left: "TreeNode | None" = None,
right: "TreeNode | None" = None) -> None:
self.val = val
self.left = left
self.right = right
#── Iterative Traversal ──────────────────────────────────────────────────────────────
def preorder_iterative(root: TreeNode | None) -> list[int]:
"""Iterative Preorder Traversal: Current → Left → Right.
Key Idea:
- Start by putting the root in the stack
- pop → visit → push right → push left
- Push left later so it's processed first (LIFO)
Time: O(n)
Space: O(h)"""
if root is None:
return []
result: list[int] = []
stack: list[TreeNode] = [root]
while stack:
node = stack.pop()
result.append(node.val)
#Push right first → left is popped first
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)
return result
def inorder_iterative(root: TreeNode | None) -> list[int]:
"""Iterative Inorder Traversal: Left → Current → Right.
Key Idea:
- Move to the leftmost node using a curr pointer while pushing onto the stack
- Reach the leftmost → pop → visit → curr = curr.right
- Repeat for the right subtree
Time: O(n)
Space: O(h)"""
result: list[int] = []
stack: list[TreeNode] = []
curr: TreeNode | None = root
while curr or stack:
#Move to the far left
while curr:
stack.append(curr)
curr = curr.left
#Visit and move right
curr = stack.pop()
result.append(curr.val)
curr = curr.right
return result
def postorder_iterative(root: TreeNode | None) -> list[int]:
"""Iterative Postorder Traversal: Left → Right → Current.
Key Idea (Using Reverse):
- Reverse a preorder traversal (Current→Right→Left) to get postorder
- That is, pop from stack → add to result → push left → push right
- Reverse the result at the end
Time: O(n)
Space: O(h)"""
if root is None:
return []
result: list[int] = []
stack: list[TreeNode] = [root]
while stack:
node = stack.pop()
result.append(node.val) #Current visit (reverse at the end since it's in reverse order)
if node.left: #Push left first (right is popped first)
stack.append(node.left)
if node.right:
stack.append(node.right)
result.reverse() #Current → Right → Left → Reverse → Left → Right → Current
return result
def postorder_two_stack(root: TreeNode | None) -> list[int]:
"""Postorder Traversal Using Two Stacks (For Educational Purpose).
Time: O(n)
Space: O(n) — the second stack can store up to n elements"""
if root is None:
return []
stack1: list[TreeNode] = [root]
stack2: list[int] = []
while stack1:
node = stack1.pop()
stack2.append(node.val)
if node.left:
stack1.append(node.left)
if node.right:
stack1.append(node.right)
return stack2[::-1]
#── Helper: Create tree from list ─────────────────────────────────────────────────────
def build(values: list[int | None]) -> TreeNode | None:
if not values or values[0] is None:
return None
root = TreeNode(values[0])
q: deque[TreeNode] = deque([root])
i = 1
while q and i < len(values):
node = q.popleft()
if i < len(values) and values[i] is not None:
node.left = TreeNode(values[i])
q.append(node.left)
i += 1
if i < len(values) and values[i] is not None:
node.right = TreeNode(values[i])
q.append(node.right)
i += 1
return root
#── Example Execution ──────────────────────────────────────────────────────────────
if __name__ == "__main__":
# 1
# / \
# 2 3
# / \ / \
# 4 5 6 7
root = build([1, 2, 3, 4, 5, 6, 7])
print("=== Iterative (Stack) Traversal Results ===")
print(f"Pre-order: {preorder_iterative(root)}")
print(f"In-order: {inorder_iterative(root)}")
print(f"Post-order: {postorder_iterative(root)}")
print(f"Post-order (Two Stacks): {postorder_two_stack(root)}")
print("\n=== Recursive vs Iterative Result Comparison ===")
from src.02_recursive_traversal import preorder, inorder, postorder # noqa
#Direct comparison (should be the same)
assert preorder_iterative(root) == [1, 2, 4, 5, 3, 6, 7]
assert inorder_iterative(root) == [4, 2, 5, 1, 6, 3, 7]
assert postorder_iterative(root) == [4, 5, 2, 6, 7, 3, 1]
print("All results match ✓")
#Skewed tree (left-only)
print("\n=== Skewed Tree ===")
# 1
# \
# 2
# \
# 3
skewed = build([1, None, 2, None, None, None, 3])
#Direct construction
skewed = TreeNode(1)
skewed.right = TreeNode(2)
skewed.right.right = TreeNode(3)
print(f"In-order (right-skewed): {inorder_iterative(skewed)}")
src/04_level_order.pyPython
#Topic: Level Order Traversal (BFS), Grouping by Level, Zigzag Traversal
#Time: O(n) — Each node visited exactly once
#Space: O(w) — w = maximum width (in a complete binary tree w ≈ n/2)
from __future__ import annotations
from collections import deque
class TreeNode:
def __init__(self, val: int = 0,
left: "TreeNode | None" = None,
right: "TreeNode | None" = None) -> None:
self.val = val
self.left = left
self.right = right
def level_order(root: TreeNode | None) -> list[int]:
"""Basic Level Order Traversal (BFS).
Time: O(n)
Space: O(w) — max size of queue = maximum width"""
if root is None:
return []
result: list[int] = []
queue: deque[TreeNode] = deque([root])
while queue:
node = queue.popleft()
result.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return result
def level_order_by_levels(root: TreeNode | None) -> list[list[int]]:
"""Level-wise Grouping BFS.
Key: Fix the current level size with len(queue) at the start of the loop
Time: O(n)
Space: O(w)"""
if root is None:
return []
result: list[list[int]] = []
queue: deque[TreeNode] = deque([root])
while queue:
level_size = len(queue) #Current number of nodes at the level (important!)
level: list[int] = []
for _ in range(level_size):
node = queue.popleft()
level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(level)
return result
def zigzag_level_order(root: TreeNode | None) -> list[list[int]]:
"""Zigzag (Spiral) Level Order Traversal.
Even levels: left → right (forward)
Odd levels: right → left (reverse)
Time: O(n)
Space: O(w)"""
if root is None:
return []
result: list[list[int]] = []
queue: deque[TreeNode] = deque([root])
left_to_right = True #First level is forward
while queue:
level_size = len(queue)
level: list[int] = []
for _ in range(level_size):
node = queue.popleft()
level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
if not left_to_right:
level.reverse() #Reverse level is reversed
result.append(level)
left_to_right = not left_to_right #Change of direction
return result
def right_side_view(root: TreeNode | None) -> list[int]:
"""Nodes visible from the right (last node of each level).
Time: O(n)
Space: O(w)"""
if root is None:
return []
result: list[int] = []
queue: deque[TreeNode] = deque([root])
while queue:
level_size = len(queue)
for i in range(level_size):
node = queue.popleft()
if i == level_size - 1: #Last node of the level
result.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return result
def max_depth_bfs(root: TreeNode | None) -> int:
"""Find Maximum Depth of Tree using BFS.
Time: O(n)
Space: O(w)"""
if root is None:
return 0
depth = 0
queue: deque[TreeNode] = deque([root])
while queue:
depth += 1
for _ in range(len(queue)):
node = queue.popleft()
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return depth
#── Helper: Create tree from list ─────────────────────────────────────────────────────
def build(values: list[int | None]) -> TreeNode | None:
if not values or values[0] is None:
return None
root = TreeNode(values[0])
q: deque[TreeNode] = deque([root])
i = 1
while q and i < len(values):
node = q.popleft()
if i < len(values) and values[i] is not None:
node.left = TreeNode(values[i])
q.append(node.left)
i += 1
if i < len(values) and values[i] is not None:
node.right = TreeNode(values[i])
q.append(node.right)
i += 1
return root
#── Example Execution ──────────────────────────────────────────────────────────────
if __name__ == "__main__":
# 1
# / \
# 2 3
# / \ \
# 4 5 6
root = build([1, 2, 3, 4, 5, None, 6])
print("=== Level Order Traversal ===")
print(f"Basic BFS: {level_order(root)}")
print(f"Grouped by Levels: {level_order_by_levels(root)}")
print(f"Zigzag Traversal: {zigzag_level_order(root)}")
print(f"Right Side View: {right_side_view(root)}")
print(f"Maximum Depth (BFS): {max_depth_bfs(root)}")
print()
print("=== Complete Binary Tree ===")
# 1
# / \
# 2 3
# / \ / \
# 4 5 6 7
full = build([1, 2, 3, 4, 5, 6, 7])
levels = level_order_by_levels(full)
for i, lv in enumerate(levels):
print(f"Level {i}: {lv}")
print()
print("=== Zigzag Visualization ===")
#1 Level 0: → [1]
# / \
#2 3 Level 1: ← [3, 2]
# / \ / \
#4 5 6 7 Level 2: → [4, 5, 6, 7]
zz = zigzag_level_order(full)
for i, lv in enumerate(zz):
direction = "→" if i % 2 == 0 else "←"
print(f"Level {i} ({direction}): {lv}")
Assignment
Problem 1: Baekjoon 1991 — Tree Traversal (Silver I)
In a tree with root 1, find the parent of each node.
Input format
7
1 6
6 3
3 5
4 1
2 4
4 7
Output Format
4
6
1
3
1
4
(In order of parents of nodes 2 through n)
Key Points
Input as undirected graph → traverse from root (1) With-BFS/DFS
Update the parent array parent[v] = u
Must mark visited (to prevent infinite loops)
Time/Space Complexity
Time: O(n) — BFS/DFS
Space: O(n) — adjacency list + parent array
Solution File
boj_1991.py
boj_11725.py
Show the reference solution
Try the assignment yourself first — comparing your attempt with a working solution is where most of the learning happens.
boj_11725.pyPython
#Baekjoon 11725 — Finding the Parent of a Tree
# https://www.acmicpc.net/problem/11725
# Time: O(n) Space: O(n)
import sys
from collections import deque
input = sys.stdin.readline
def solve() -> None:
n = int(input())
graph: list[list[int]] = [[] for _ in range(n + 1)]
for _ in range(n - 1):
u, v = map(int, input().split())
graph[u].append(v)
graph[v].append(u)
parent = [0] * (n + 1)
visited = [False] * (n + 1)
#BFS: Start from the root (1)
queue: deque[int] = deque([1])
visited[1] = True
while queue:
node = queue.popleft()
for neighbor in graph[node]:
if not visited[neighbor]:
visited[neighbor] = True
parent[neighbor] = node
queue.append(neighbor)
#Print the parents of nodes 2 to n
result = [str(parent[i]) for i in range(2, n + 1)]
sys.stdout.write("\n".join(result) + "\n")
solve()
boj_1991.pyPython
#Baekjoon 1991 — Tree Traversal
# https://www.acmicpc.net/problem/1991
# Time: O(n) Space: O(n)
import sys
input = sys.stdin.readline
def solve() -> None:
n = int(input())
tree: dict[str, tuple[str | None, str | None]] = {}
for _ in range(n):
parts = input().split()
node = parts[0]
left = parts[1] if parts[1] != "." else None
right = parts[2] if parts[2] != "." else None
tree[node] = (left, right)
def preorder(node: str | None) -> None:
if node is None:
return
print(node, end="")
left, right = tree[node]
preorder(left)
preorder(right)
def inorder(node: str | None) -> None:
if node is None:
return
left, right = tree[node]
inorder(left)
print(node, end="")
inorder(right)
def postorder(node: str | None) -> None:
if node is None:
return
left, right = tree[node]
postorder(left)
postorder(right)
print(node, end="")
preorder("A")
print()
inorder("A")
print()
postorder("A")
print()
solve()