Ran Wei/CS Series/07
中文
Computer Science Fundamentals — Ran Wei

Module 07: Recursion, trees and graphs

Follow recursive calls and traverse trees and graphs. Build a route finder and state exactly what makes its path shortest.

≈ 5 hours4 sessions2 labs8 exercises6 quiz questions

By the end you can

  • Identify recursive base cases and progress.
  • Trace call stacks.
  • Traverse binary trees.
  • Implement BFS with visited state.
  • Choose BFS or weighted shortest paths appropriately.

Before you start

Recommended modules: 04, 06.

Know stacks, queues and dictionary membership. A tuple groups values; None represents an empty subtree.

Contents

Study plan

5 hours

Four 75-minute sessions including practice. Allow longer for extensions or unfamiliar prerequisites. Progress is saved locally and shared between language editions.

Session 175 min
Concepts and worked examples
Session 375 min
Apply and extend
1

Recursion and the call stack

A recursive function calls itself on a smaller subproblem. A base case returns without another call. For factorial, fact(0)=1 and fact(n)=n×fact(n−1) for nonnegative n. Each pending call keeps local state on the call stack; returning unwinds those frames. Show that a nonnegative measure decreases, otherwise a base case alone does not guarantee termination. Deep recursion also consumes stack memory; Python limits depth, so an iterative solution or explicit stack may be preferable for large inputs.

Check your understanding

Why is fact(n+1) instead of fact(n−1) a problem?

Worked solution

The argument moves away from zero, so the base case is not reached.

2

Trees and traversals

A rooted tree has a distinguished root and parent-child structure without cycles. A binary tree has at most two children per node. Preorder visits node, left, right; inorder visits left, node, right; postorder visits left, right, node. In a binary search tree, all left keys precede the node and right keys follow it, so inorder yields sorted keys. Height controls search cost: balanced trees have logarithmic height, while a chain can have linear height. The lab constructs immutable nested tuples rather than a full mutable search-tree implementation.

Check your understanding

Does inorder sort every arbitrary binary tree?

Worked solution

No, only trees satisfying the search-order property.

3

Graphs and representation

A graph has vertices and edges. Edges may be directed, undirected or weighted. An adjacency list stores neighbours for each vertex, using O(V+E) storage in the usual model; an adjacency matrix uses O(V²) and supports direct edge checks. Unlike a tree, a graph can have cycles and multiple paths, so traversal needs visited state. Mark a vertex when scheduling it to avoid adding the same vertex repeatedly. Specify whether isolated vertices, unknown endpoints and parallel edges are allowed.

Check your understanding

Why mark visited on enqueue rather than only on removal?

Worked solution

To prevent multiple predecessors from scheduling the same vertex.

4

DFS and BFS

Depth-first search follows a branch before alternatives, using recursion or an explicit stack. Breadth-first search uses a queue and explores vertices in nondecreasing edge distance from the start. With adjacency lists, both traverse reachable vertices and edges in O(V+E), assuming constant-time visited checks. BFS stores a predecessor when first discovering each vertex. Following predecessors backwards reconstructs a route. DFS finds a route but does not generally minimise its edge count.

Check your understanding

Which frontier structure gives BFS its order?

Worked solution

A FIFO queue.

5

Weighted paths and limits

BFS minimises edges only when each edge has the same cost. For nonnegative varying weights, Dijkstra repeatedly finalises the vertex with the smallest tentative distance and relaxes outgoing edges. A priority queue supports efficient selection. A direct edge of cost 100 loses to two edges costing 1 each, despite having fewer edges. Negative weights violate Dijkstra's finalisation argument; other algorithms are needed. State the objective before calling a route shortest, and distinguish unreachable from unknown vertices.

Check your understanding

Does BFS minimise travel time when road times vary?

Worked solution

No, it minimises edge count, not varying total time.

6

Common misconceptions

  • The returned-list tree lab is illustrative; repeated concatenation is not an optimal traversal.
  • A shortest route must specify its cost model.
7

Lab setup

Download each script and run it in a terminal with Python 3.11 or later: python m07_tree.py. On Windows, py -3 is an alternative; on some systems use python3. The labs use only the standard library. Predict the result before running, then complete the variations. Run without -O so assertions remain enabled. Outputs below were captured by the builder. Code and output are identical in both language editions.

8

Lab 1 — Recursive trees

Draw the tree, predict traversal and height, then follow the base case for None.

Download m07_tree.py

"""Recursive traversal with an explicit empty-tree base case."""
def inorder(node):
    if node is None:
        return []
    value, left, right = node
    return inorder(left) + [value] + inorder(right)

def height(node):
    if node is None:
        return 0
    return 1 + max(height(node[1]), height(node[2]))

tree = (4, (2, (1, None, None), (3, None, None)), (6, None, None))
print("inorder:", inorder(tree))
print("height:", height(tree))
assert inorder(tree) == [1, 2, 3, 4, 6]
assert inorder(None) == [] and height(None) == 0
Captured output
inorder: [1, 2, 3, 4, 6]
height: 3
  1. Implement preorder.
  2. Make a three-node chain.
  3. Explain why concatenating lists repeatedly adds copying cost.
Worked solution

Preorder is [value]+preorder(left)+preorder(right). A three-node chain has height 3. The returned-list implementation copies partial outputs repeatedly and can be quadratic on a skewed tree; an accumulator traversal appending each item once avoids that.

9

Lab 2 — A route finder

The graph includes cycles and an isolated vertex. BFS records parents at discovery and returns None for an unreachable target.

Download m07_routes.py

"""BFS finds a shortest path measured in edges in an unweighted graph."""
from collections import deque
def route(graph, start, goal):
    if start not in graph or goal not in graph:
        raise KeyError("unknown vertex")
    queue, parent = deque([start]), {start: None}
    while queue:
        vertex = queue.popleft()
        if vertex == goal:
            path = []
            while vertex is not None:
                path.append(vertex); vertex = parent[vertex]
            return path[::-1]
        for neighbour in graph[vertex]:
            if neighbour not in parent:
                parent[neighbour] = vertex; queue.append(neighbour)
    return None

graph = {"A": ["B", "C"], "B": ["A", "D"], "C": ["A", "D"], "D": ["B", "C"], "E": []}
print("A to D:", route(graph, "A", "D"))
print("A to E:", route(graph, "A", "E"))
assert route(graph, "A", "D") == ["A", "B", "D"]
assert route(graph, "A", "A") == ["A"]
assert route(graph, "A", "E") is None
Captured output
A to D: ['A', 'B', 'D']
A to E: None
  1. Swap A's neighbour order.
  2. Add E to D's neighbours in both directions.
  3. Pass an unknown vertex and compare with unreachable E.
Worked solution

The alternate equally short route is A,C,D. Connecting E gives A,B,D,E. Unknown vertices raise KeyError; valid but disconnected vertices return None. Ties depend on neighbour order, not on a different distance.

10

Exercises with worked solutions

Try before opening the solution. ★ applies an idea; ★★ combines ideas; ★★★ asks for design or proof.

Exercise 1 — Base case★

Give the base case for counting nodes in an empty tree.

Worked solution

Return 0 for None; otherwise 1+count(left)+count(right).

Exercise 2 — Traversal★

For root B with children A,C, give inorder and preorder.

Worked solution

Inorder A,B,C; preorder B,A,C.

Exercise 3 — Cycle★★

Why can DFS loop forever on A→B→A without visited state?

Worked solution

Each recursive step revisits the other vertex, with no decreasing unvisited set or exit. Mark visits before exploring neighbours.

Exercise 4 — Storage★★

Which representation is smaller for a sparse graph?

Worked solution

An adjacency list, O(V+E), rather than an O(V²) matrix when E is much smaller than V².

Exercise 5 — Weighted counterexample★★

A→C costs 10; A→B→C costs 2+2. Which does BFS favour?

Worked solution

Direct A→C has one edge; it is not minimum cost. Weighted shortest path should select total cost 4.

Exercise 6 — BFS proof★★★

Why is a first-discovered BFS path shortest in edges?

Worked solution

The queue processes all vertices at distance d before d+1. New neighbours get distance d+1; any shorter predecessor would already have been processed, so a shorter discovery cannot arrive later.

Exercise 7 — Stack space★★★

Compare recursive traversal stack depth in balanced and chain trees.

Worked solution

Depth equals height: O(log n) for a balanced tree, O(n) for a chain. Both still visit n nodes.

Exercise 8 — Path reconstruction★★

Why reverse the accumulated parent path?

Worked solution

Following parents starts at goal and ends at start; reversing gives the requested start-to-goal order.

11

Self-check quiz

Choose an answer for feedback; reset to retry. A text answer key is available without JavaScript.

1

Recursion needs?

2

BFS uses?

3

BFS minimises?

4

Dijkstra requires?

5

A graph traversal needs visited state because?

6

Inorder yields sorted keys when?

Answer key
  1. A — Progress makes the base reachable.
  2. B — FIFO expands layers.
  3. C — Equal-cost edges make edge distance meaningful.
  4. A — Negative weights break finalisation.
  5. B — It prevents repeated scheduling.
  6. C — The left/right key invariant is necessary.
12

Guided reading

13

Review and the next step

Trace parents for an unreachable and a tied shortest path. Explain the recursive empty case. Module 08 compares design strategies beyond traversal.

14

Key terms

TermMeaning
FrontierDiscovered vertices awaiting processing.
RelaxationImproving a tentative distance via an edge.
HeightHere, the maximum number of nodes on a root-to-leaf path.