UID - 2305035 Date:07/07/2025
Practical: 3 Graphs
AIM: A) Implementing Search Algorithms Hands-on coding of search
algorithms (e.g., BFS, DFS, A*)
CONCEPTS:
1.Graph Representation The graph is stored as a dictionary
where keys are nodes and values are
lists of neighbors.
2. Breadth-First Search (BFS) A graph traversal algorithm that
explores all neighbors level by level
using a queue.
3. Queue (deque) A double-ended queue from
collections used to store paths to
be explored in FIFO order.
4. Visited Set A set is used to keep track of visited
nodes to avoid cycles and repeated
work.
5. Path Tracking
Each element in the queue is a list
that represents the path from the start
node to the current node.
6.Goal Check After popping a path, the code checks
if the last node is the goal. If yes, it
returns the path.
7. Neighbor Expansion If the current node isn’t the goal, all its
unvisited neighbors are added to the
queue with updated paths.
PSEUDO CODE:
Function BFS(graph, start, goal):
Create an empty set called visited
Create a queue and add [start] as the first path
While the queue is not empty:
Remove the first path from the queue
Set node as the last item in the path
If node equals goal:
Return the path
If node is not in visited:
Add node to visited
For each neighbor of node in the graph:
Make a copy of the current path
Add the neighbor to the copied path
Add the new path to the end of the queue
If the goal is not found:
Return None
CODE:
from collections import deque
def bfs(graph, start, goal):
visited = set()
queue = deque([[start]])
while queue:
path = queue.popleft()
node = path[-1]
if node == goal:
return path
if node not in visited:
visited.add(node)
for neighbor in graph.get(node, []):
new_path = list(path)
new_path.append(neighbor)
queue.append(new_path)
return None
graph = {
'A': ['B', 'C', 'D'],
'B': ['E', 'F'],
'C': ['G'],
'D': ['H', 'I'],
'E': ['J', 'K'],
'F': [],
'G': ['L'],
'H': [],
'I': ['M'],
'J': [],
'K': ['N'],
'L': [],
'M': [],
'N': []
}
path = bfs(graph, 'A', 'N')
print("Path to goal:", " → ".join(path))
OUTPUT:
AIM: B)Implementing DFS algorithm
CONCEPTS:
1. Depth First Search (DFS) A graph traversal algorithm that
explores one path completely before
moving to another path.
2. Stack (List of Paths) DFS uses a stack to store and explore
paths. The last path pushed is
explored first (LIFO).
3. Reversed Neighbors Neighbors are reversed before
pushing to the stack so the order
matches diagrams (left-to-right).
4. Path Tracking
Each element in the queue is a list
that represents the path from the start
node to the current node.
PSEUDO CODE:
Function DFS(graph, start, goal):
Create an empty set called visited
Create a stack and push a list containing the start node
While stack is not empty:
Pop the top path from the stack
Let node be the last element in the path
If node is the goal:
Return path
If node is not in visited:
Add node to visited
For each neighbor of node in reversed order:
Create a new path by copying current path
Append neighbor to new path
Push new path to the stack
Return None // If goal is not found
CODE:
def dfs(graph, start, goal):
stack = [[start]] # Stack of paths
visited = set()
while stack:
path = stack.pop()
node = path[-1]
if node == goal:
return path
if node not in visited:
visited.add(node)
for neighbor in reversed(graph.get(node, [])): # Reverse to
# match diagram order
new_path = list(path)
new_path.append(neighbor)
stack.append(new_path)
# If the goal is never found, return None.
return None # No path found
graph = {
'A': ['B', 'C'],
'B': ['D', 'E'],
'C': ['F', 'G'],
'D': [],
'E': [],
'F': [],
'G': []
}
path = dfs(graph, 'A', 'D')
print("DFS Path:", " → ".join(path))
OUTPUT:
Aim : C)Implementing A* algorithm
CONCEPTS:
CONCEPTS:
1.Graph (with weights) The graph is represented using a
dictionary. Each node points to a list of
neighbors and the cost to reach them.
2. Heuristic (h) A dictionary (h) that stores the
estimated cost from each node to the
goal. Helps guide the search faster
3. Priority Queue (heapq) heapq is used to always pick the
node with the lowest estimated total
cost (f = g + h).
4. g_score
Actual cost from the start node to the
current node. Starts with 0 for the start
node and infinity for others.
5. f_score
Estimated total cost from start →
current → goal. Calculated as f = g
+ h. Used to sort nodes in the queue.
6. Came_from
A dictionary that remembers the best path
(previous node) to each visited node — used
to rebuild the final path.
7. Path Reconstruction
After reaching the goal, the
reconstruct_path() function backtracks
using came_from to print the full path.
PSEUDO CODE:
Create an empty priority queue called open_set
Add (0, start) into open_set
Create an empty dictionary called came_from
Set g_score of all nodes to infinity
Set g_score[start] = 0
Set f_score of all nodes to infinity
Set f_score[start] = heuristic[start]
While open_set is not empty:
Remove the node with the lowest f_score → current
If current equals goal:
Return the path (using reconstruct_path) and the total cost
For each neighbor and its cost of current in the graph:
Calculate tentative_g = g_score[current] + cost
If tentative_g < g_score[neighbor]:
Update came_from[neighbor] = current
Update g_score[neighbor] = tentative_g
Update f_score[neighbor] = tentative_g + heuristic[neighbor]
Add (f_score[neighbor], neighbor) to open_set
If goal is not found:
Return None and infinity as cost
CODE:
import heapq
def a_star(graph, start, goal, h):
open_set = []
heapq.heappush(open_set, (0, start))
came_from = {}
g_score = {node: float('inf') for node in graph}
g_score[start] = 0
f_score = {node: float('inf') for node in graph}
f_score[start] = h[start]
while open_set:
current_f, current = heapq.heappop(open_set)
if current == goal:
return reconstruct_path(came_from, current), g_score[goal]
for neighbor, cost in graph[current]:
tentative_g = g_score[current] + cost
if tentative_g < g_score[neighbor]:
came_from[neighbor] = current
g_score[neighbor] = tentative_g
f_score[neighbor] = tentative_g + h[neighbor]
heapq.heappush(open_set, (f_score[neighbor], neighbor))
return None, float('inf')
def reconstruct_path(came_from, current):
path = [current]
while current in came_from:
current = came_from[current]
path.append(current)
path.reverse()
return path
# Graph and heuristic from your image
graph = {
'A': [('B', 1), ('C', 4)],
'B': [('D', 3)],
'C': [('E', 5)],
'D': [('F', 2)],
'E': [('G', 3)],
'F': [('G', 1)],
'G': []
}
h = {
'A': 5,
'B': 6,
'C': 4,
'D': 3,
'E': 3,
'F': 1,
'G': 0
}
# Run A* from A to G
path, cost = a_star(graph, 'A', 'G', h)
print("Path:", " → ".join(path))
print("Total Cost:", cost)