Lab: Search Algorithms in Python
Unit 3: Search Techniques for Problem Solving — Lab
In this lab, you will implement two search algorithms from scratch in Python, then verify their behavior against known correct answers. By the end of the lab, you will have working BFS and A* code that you can adapt for any graph or grid problem.
Learning Objectives
By completing this lab, you will be able to:
-
Implement breadth-first search (BFS) on an explicit graph and return the correct path
-
Implement A* search on a grid using Manhattan distance as the heuristic
-
Explain why BFS finds the shortest hop-count path and A* finds the lowest-cost path
-
Verify algorithm correctness using provided test cases
-
Interpret the difference in nodes expanded between BFS and A*
Prerequisite concepts from this unit:
-
BFS (Section 3.3) — FIFO frontier, level-by-level expansion, optimal for uniform costs
-
A* (Section 3.4) — priority queue ordered by f(n) = g(n) + h(n), optimal with admissible h
-
Admissibility (Section 3.5) — h(n) ≤ h*(n) for all n; Manhattan distance is admissible on grids with 4-directional movement
-
Graph search (Section 3.2) — explored set prevents re-expansion of visited states
Setup
This lab runs in Google Colab (no local installation required) or any Jupyter-compatible environment with Python 3.8+.
Option A — Google Colab (recommended):
-
Select File → Upload notebook
-
Upload the
Search_Lab_Student.ipynbfile (download below) -
Run Runtime → Run all to confirm the environment loads
Option B — Local Jupyter:
-
Install dependencies:
pip install notebook -
Launch:
jupyter notebook Search_Lab_Student.ipynb
Starter Notebook:
The notebook contains:
-
All import statements and helper functions
-
Four task cells (Tasks 1–4) with
# YOUR CODE HEREmarkers -
Six test cells that check your implementation automatically
-
Markdown cells explaining each task
Lab Tasks Overview
The four tasks:
-
Task 1: Implement BFS on a small city map graph — return the path from source to destination
-
Task 2: Add path-cost tracking to your BFS and verify it finds the minimum-hop path
-
Task 3: Implement A* on a 10×10 grid with obstacles — use Manhattan distance as h(n)
-
Task 4: Compare BFS and A* on the same grid — count nodes expanded and explain the difference
Task Descriptions
Task 1: BFS on a Graph
You are given a Python dictionary representing a city map:
city_map = {
'A': ['B', 'C'],
'B': ['A', 'D', 'E'],
'C': ['A', 'F'],
'D': ['B'],
'E': ['B', 'F'],
'F': ['C', 'E', 'G'],
'G': ['F']
}
Implement a function bfs(graph, start, goal) that returns the path from start to goal as a list of node names.
Your implementation must use a FIFO queue (the collections.deque class) and maintain an explored set to avoid revisiting nodes.
Expected output: bfs(city_map, 'A', 'G') should return ['A', 'C', 'F', 'G'] (3 hops — the shortest path).
Task 2: Verify BFS Optimality
The test cell for Task 2 checks multiple source/destination pairs and verifies that your BFS always returns the minimum-hop path. You do not need to write new code for this task — just confirm all tests pass after completing Task 1.
If a test fails, examine your BFS implementation:
-
Are you using a deque correctly (append to right, popleft from left)?
-
Are you tracking the explored set and the path correctly?
-
Are you returning the path by tracing parent pointers, not just the goal node?
Task 3: A* on a Grid
You are given a 10×10 grid represented as a 2D list where 0 = open and 1 = obstacle.
The agent starts at (0, 0) (top-left) and must reach (9, 9) (bottom-right).
Movement is 4-directional (up, down, left, right); each move costs 1.
Implement a_star(grid, start, goal) that:
-
Uses a min-priority queue (the
heapqmodule) ordered by f(n) = g(n) + h(n) -
Uses Manhattan distance as the heuristic: h(r, c) = |r - goal_r| + |c - goal_c|
-
Maintains an explored set (graph search)
-
Returns the path as a list of (row, col) tuples, or
Noneif no path exists
Tip: Python’s heapq provides a min-heap. Push tuples (f_value, g_value, state, path) to keep the priority queue ordered by f.
Task 4: BFS vs. A* Node Count Comparison
Run both algorithms on the provided test grid and record the number of nodes each algorithm expands. Answer the reflection questions in the notebook Markdown cell:
-
Which algorithm expanded more nodes?
-
Why does A* expand fewer nodes on this grid?
-
What would happen to A* if the heuristic were zero (h(n) = 0)?
-
Is Manhattan distance still admissible if diagonal moves were allowed? Why or why not?
Grading Criteria
| Component | Description | Points |
|---|---|---|
Task 1: BFS implementation |
Correct path returned; uses deque; maintains explored set |
25 |
Task 2: BFS tests pass |
All 5 BFS test cases return minimum-hop paths |
15 |
Task 3: A* implementation |
Correct path returned; uses heapq with f(n) = g(n) + h(n); graph search |
35 |
Task 4: Reflection |
Node counts recorded; all 4 reflection questions answered substantively (2–3 sentences each) |
25 |
Total |
100 |
Common pitfalls:
-
BFS: Forgetting to add a node to the explored set when it is added to the frontier (not when expanded) can cause BFS to explore the same node multiple times.
-
A*: Using a regular Python list as a priority queue instead of
heapqwill produce incorrect expansion order. Always useheapq.heappushandheapq.heappop. -
Both: Make sure you reconstruct the full path from start to goal, not just return the goal node. Use a dictionary mapping each state to its parent.
Submission Instructions
-
Complete all four tasks in the notebook
-
Confirm all test cells show "PASS" (or explain any failures in a comment)
-
Download your completed notebook: File → Download → Download .ipynb
-
Submit via the Unit 3 Lab assignment in the course LMS
Want to see these algorithms visualized?
-
PathFinding.js (MIT) — interactive BFS, DFS, A*, and more on a grid
-
Red Blob Games A* tutorial — the clearest visual walkthrough of A* available online
-
aima-python on GitHub (MIT) — the full search module from the AIMA textbook; great for exploring more algorithms
Lab code adapted from aima-python, MIT License, Copyright 2016 aima-python contributors.
This work is licensed under CC BY-SA 4.0.