Programming lesson
Mastering Search Algorithms in AI: A Hands-On Guide for CS6601 Assignment 1
Learn how to implement BFS, DFS, and A* search algorithms in Python for the CS6601 Assignment 1 Romania graph problem. Step-by-step tutorial with code examples and real-world AI applications.
Introduction to Search in Artificial Intelligence
Search algorithms are the backbone of problem-solving in artificial intelligence. From planning a road trip to training a chatbot, search helps AI systems find optimal solutions when the path isn't obvious. In this tutorial, we'll explore the core search algorithms you'll implement for CS6601 Assignment 1: Breadth-First Search (BFS), Depth-First Search (DFS), and A* search. We'll use the Romania map as our testing ground, just like the assignment, and connect these concepts to real-world trends like AI-powered navigation apps and gaming pathfinding.
Why Search Matters in AI
Search is everywhere in AI. When you use Google Maps to find the fastest route, it runs search algorithms under the hood. In gaming, characters navigate complex terrains using pathfinding. Even large language models like ChatGPT use search during inference to generate coherent responses. Understanding search gives you a foundation for more advanced AI topics like reinforcement learning and planning.
For this assignment, you'll implement search algorithms that calculate a route between two cities in Romania while minimizing time and space costs. The Romania graph is an undirected network representing real cities and roads. You'll also have the option to test on an Atlanta graph for the Race! challenge.
Setting Up Your Environment
Before diving into code, let's set up your development environment. Follow these steps to avoid common pitfalls:
- Create a Conda environment:
conda create --name a1_env python=3.9 -y - Activate it:
conda activate a1_env - Install dependencies:
pip install -r requirements.txt - Launch Jupyter:
jupyter notebook
If you face kernel issues, remember to start Jupyter from the activated environment. Always run all dependent cells before executing a cell — Jupyter doesn't automatically track dependencies like an IDE. Use 'Run All' from the Cell menu to refresh state.
Understanding the Romania Graph
The Romania graph is a classic AI benchmark. It consists of cities like Arad, Bucharest, and Timisoara connected by roads with distances. For example, the distance between Arad and Sibiu is 140 km. You'll work with an adjacency list representation where each node has neighbors and edge costs.
Here's a snippet to load the graph:
import pickle
with open('romania_graph.pkl', 'rb') as f:
graph = pickle.load(f)
print(graph['Arad'])
# Output: [('Sibiu', 140), ('Timisoara', 118), ('Zerind', 75)]The graph is undirected, so connections go both ways. Your search algorithms must handle this correctly.
Implementing Search Algorithms
Breadth-First Search (BFS)
BFS explores the graph level by level, guaranteeing the shortest path in terms of number of edges. It uses a queue (FIFO) to manage the frontier. For the Romania graph, BFS finds the path with the fewest cities visited, but not necessarily the shortest distance.
from collections import deque
def bfs(graph, start, goal):
frontier = deque([start])
visited = set()
parent = {start: None}
while frontier:
node = frontier.popleft()
if node == goal:
break
for neighbor, _ in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
parent[neighbor] = node
frontier.append(neighbor)
# Reconstruct path
path = []
node = goal
while node is not None:
path.append(node)
node = parent[node]
return path[::-1]BFS is great for unweighted graphs but can be memory-intensive. For the Romania graph with 20 cities, it's manageable.
Depth-First Search (DFS)
DFS explores as far as possible along each branch before backtracking. It uses a stack (LIFO). DFS may find a solution quickly but not necessarily the shortest. It's useful when memory is limited.
def dfs(graph, start, goal):
stack = [start]
visited = set()
parent = {start: None}
while stack:
node = stack.pop()
if node == goal:
break
if node not in visited:
visited.add(node)
for neighbor, _ in graph[node]:
if neighbor not in visited:
parent[neighbor] = node
stack.append(neighbor)
# Reconstruct path
path = []
node = goal
while node is not None:
path.append(node)
node = parent[node]
return path[::-1]DFS can get stuck in infinite loops if cycles aren't handled, but our visited set prevents that.
A* Search
A* combines the best of BFS and greedy search using a heuristic. It minimizes f(n) = g(n) + h(n), where g(n) is the cost from start to n, and h(n) is the estimated cost from n to goal. For the Romania graph, we can use straight-line distance as heuristic.
import heapq
def astar(graph, start, goal, heuristic):
frontier = [(0, start)]
g_cost = {start: 0}
parent = {start: None}
while frontier:
_, node = heapq.heappop(frontier)
if node == goal:
break
for neighbor, cost in graph[node]:
new_g = g_cost[node] + cost
if neighbor not in g_cost or new_g < g_cost[neighbor]:
g_cost[neighbor] = new_g
f = new_g + heuristic(neighbor, goal)
heapq.heappush(frontier, (f, neighbor))
parent[neighbor] = node
# Reconstruct path
path = []
node = goal
while node is not None:
path.append(node)
node = parent[node]
return path[::-1]A* is optimal if the heuristic is admissible (never overestimates). Straight-line distance works perfectly for road networks.
Testing and Debugging
The assignment provides custom test files. Run them from the terminal to verify your implementations:
python search_basic_tests.py
python search_romania_tests.py SearchRomaniaTests.test_bfs_romaniaIf a test fails, use the visualizer to see where your search went wrong. Common issues include not marking visited nodes correctly or mishandling the queue/stack order.
Real-World Applications
Search algorithms power many modern technologies. For instance, AI in self-driving cars uses A* for path planning. In gaming, NPCs navigate using BFS or A*. Even social media recommendation systems use BFS to find connections. Recently, AI chatbots like ChatGPT use search during chain-of-thought reasoning to improve answers.
In the context of the 2026 FIFA World Cup, imagine planning the shortest route between stadiums in different cities — that's exactly what you're coding for Romania!
Tips for Success
- Always run 'Run All' before testing to ensure state consistency.
- Use the provided visualizers to debug path choices.
- Test on the Atlanta graph for extra practice.
- Review the rubric: points are awarded for correctness, efficiency, and code clarity.
Conclusion
Search algorithms are a fundamental tool in AI. By implementing BFS, DFS, and A* for the Romania graph, you're gaining skills applicable to navigation, gaming, and AI planning. As you work through CS6601 Assignment 1, remember that these algorithms are not just academic — they're used daily in apps like Google Maps and Uber. Good luck!