0% found this document useful (0 votes)
25 views22 pages

AIML Lab Programs1-9

The document outlines various Python programs implementing different search algorithms including Breadth First Search, Depth First Search, Water Jug Problem, Uniform Cost Search, Depth Limited Search, and Iterative Deepening Depth First Search. Each algorithm is presented with a step-by-step algorithm description followed by a sample implementation in Python. The document provides example graphs and expected outputs for each program, illustrating how the algorithms function.

Uploaded by

shreyassupe346
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
25 views22 pages

AIML Lab Programs1-9

The document outlines various Python programs implementing different search algorithms including Breadth First Search, Depth First Search, Water Jug Problem, Uniform Cost Search, Depth Limited Search, and Iterative Deepening Depth First Search. Each algorithm is presented with a step-by-step algorithm description followed by a sample implementation in Python. The document provides example graphs and expected outputs for each program, illustrating how the algorithms function.

Uploaded by

shreyassupe346
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 22

Lab Programs

1. Write a Python program to implement Breadth First Search

Algorithm:

Step 1: Initialization:

 Queue: Initialize a queue and add the starting node to it. This queue will help in exploring
nodes level by level.
 Visited Set: Create a set to keep track of visited nodes to avoid processing the same
node multiple times.

Step 2: Start the Search:

 Dequeue the front node from the queue.


 If this node is the target node, return the path or the node as the solution.
 Otherwise, explore all adjacent nodes (neighbors) of the dequeued node.

Step 3: Mark as Visited:

 For each neighbor of the current node, if it has not been visited, mark it as visited and
enqueue it. Also, keep track of the path by storing the parent of each node or the path
taken to reach the node.

Step 4: Repeat steps 2 and 3 until the queue is empty or the target node is found.

Step 5: Return the Result:

 If the target node is found, return the path or node.


 If the queue becomes empty without finding the target node, return a failure indication
(e.g., "target not found").
Program 1:

graph = {
'1': ['2', '3'],
'2': ['4', '5'],
'3': ['6', '7'],
'4': [],
'5': [],
'6': [],
'7': []
}
# BFS implementation
visited = [] # List for visited nodes.
queue = [] # Initialize a queue

def bfs(visited, graph, node, goal): # Function for BFS with


goal state
visited.append(node)
queue.append(node)

while queue:
m = queue.pop(0) # Pop the first element from the queue
print(m, end=" ")

# Check if we reached the goal


if m == goal:
print(f"\nGoal state '{goal}' found!")
return

# Loop to visit each node


for neighbour in graph[m]:
if neighbour not in visited:
visited.append(neighbour)
queue.append(neighbour)

print(f"\nGoal state '{goal}' not found in the graph.")

# Calling the BFS function starting from node '5' and searching
for goal state '8'

print("Path for Breadth First Search: ")


bfs(visited, graph, '1','5')
Output:-

Path for Breadth First Search:


1 2345
Goal state '5' found!
2. Write a Python program to implement for Depth First Search

ALGORITHM:

Step 1: Initialization:

 Stack: Initialize a stack and push the starting node onto it. This stack will help in
exploring nodes depth-first.
 Visited Set: Create a set to keep track of visited nodes to avoid processing the same
node multiple times.

Step 2: Start the Search:

 Pop the top node from the stack.


 If this node is the target node, return the path or the node as the solution.
 Otherwise, explore all adjacent nodes (neighbors) of the popped node.

Step 3: Mark as Visited:

 For each neighbor of the current node, if it has not been visited, mark it as visited and
push it onto the stack. Also, keep track of the path by storing the parent of each node or
the path taken to reach the node.

Step 4: Repeat steps 2 and 3 until the stack is empty or the target node is found. Step 5: Return

the Result:
 If the target node is found, return the path or node.
 If the stack becomes empty without finding the target node, return a failure
Program 2:

def dfs(graph, start, goal, path=None, visited=None): #


Initialize path and visited on the first call
if path is None:
path = []
if visited is None:
visited = set()

# Mark the current node as visited


visited.add(start)
path.append(start)

# Check if the current node is the goal


if start == goal:
print("Path found:", path)
print(f"\nGoal state '{goal}' found in the graph")
return True

# Explore all neighbors


for neighbor in graph[start]:
if neighbor not in visited: # Recurse to the next
neighbor
if dfs(graph, neighbor, goal, path, visited):
return True

# Backtrack: Remove the current node from path if no goal is


found
path.pop()
return False

# Example graph as an adjacency list


graph = {
'1': ['2', '3'],
'2': ['4', '5'],
'3': ['6', '7'],
'4': [],
'5': [],
'6': [],
'7': []
}
# Example usage: Find the path from 'A' to 'G'
dfs(graph, '1', '5')

OUTPUT:
Path found: ['1', '2', '5']

Goal state '5' found in the graph


3. Write a Python program to implement Water jug problem

Algorithm:
Step 1: Understand the Problem:
 You have two jugs with capacities liters and b liters.
 Your goal is to measure exactly liters of water.
 Operations allowed:
(1) Fill a jug completely.
(2) Empty a jug completely.
(3) Pour water from one jug to another until one of the jugs is either full or
empty.

Step 2:Check Solvability:


 Input: Capacities and b of the two jugs, and the target
 Output: Boolean value indicating whether the problem is solvable.

i) If > + c>a+b, return False (unsolvable).


ii) If is not a multiple of gcd( , ) return False (unsolvable).
iii) Otherwise, the problem is solvable.

Step 3: Initialize Data Structures:


 Queue: To manage the states during BFS traversal.
 Visited Set: To track visited states to avoid redundant processing.

Step 4: BFS Initialization:


 Starting State: (0,0), representing both jugs being empty.
 Add the initial state to the queue.
 Mark the initial state as visited.

Step 5: Perform BFS:


i) While the queue is not empty:
ii) Dequeue the front state from the queue (let's call it ( 1,j 2)).
iii) If 1== or 2== , return True (success).
iv) Generate all possible states from the current state by performing the
allowed operations:
1. Fill jug1: ( , j 2).
2. Fill jug2: ( 1, )
3. Empty jug1: (0, 2)
4. Empty jug2: ( 1,0)
5. Pour jug2 to jug1: ( n( ,
1+ 2), 1+ 2− ( , 1+ 2)).
6. Pour jug1 to jug2: ( 1+ 2− n( ,
1+ 2), ( , 1+ 2)).
7. For each generated state, if it has not been visited, mark it as visited
and enqueue it.
Step 6: Return the Result:

If the BFS completes without finding a state where one of the jugs contains exactly
litres, return False (failure)
Program 3:

from collections import deque

def water_jug_problem(capacity_jug1, capacity_jug2, target):


visited = set()

queue = deque([(0, 0, [])]) # Initial state: (0, 0) - both jugs


are empty

while queue:
current_state = queue.popleft()
jug1, jug2, steps = current_state

if (jug1 == target and jug2 == capacity_jug2):


return steps + [(jug1, jug2)]

if (jug1, jug2) in visited:


continue

visited.add((jug1, jug2))
#Fill jug1
queue.append((capacity_jug1, jug2, steps + [(jug1, jug2,
f"Fill jug 1 ({capacity_jug1}, {jug2})")]))

# Fill jug2
queue.append((jug1, capacity_jug2, steps + [(jug1, jug2,
f"Fill jug 2 ({jug1},{capacity_jug2})")]))

# Empty jug1
queue.append((0, jug2, steps + [(jug1, jug2, f"Empty jug 1 (0,
{jug2})")]))

# Empty jug2
queue.append((jug1, 0, steps + [(jug1, jug2, f"Empty jug 2
({jug1}, 0)")]))

# Pour jug1 to jug2


amount = min(jug1, capacity_jug2 - jug2)
queue.append((jug1 - amount, jug2 + amount,steps + [(jug1,
jug2, f"Pour jug 1 to jug 2 ({jug1 - amount}, {jug2 + amount})")]))

# Pour jug2 to jug1


amount = min(jug2, capacity_jug1 - jug1)

queue.append((jug1 + amount, jug2 - amount, steps + [(jug1,


jug2, f"Pour jug 2 to jug 1 ({jug1 + amount}, {jug2 - amount})")]))

return None # No solution found

# Example usage
capacity_jug1 = 4
capacity_jug2 = 3
target_amount = 2
solution = water_jug_problem(capacity_jug1, capacity_jug2,
target_amount)

if solution:
print("Steps to reach the desired state:")
for step in solution:
print(step)
else:
print("No solution found.")

Output:

Steps to reach the desired state:


(0, 0, 'Fill jug 1 (4, 0)')
(4, 0, 'Pour jug 1 to jug 2 (1, 3)')
(1, 3, 'Empty jug 2 (1, 0)')
(1, 0, 'Pour jug 1 to jug 2 (0, 1)')
(0, 1, 'Fill jug 1 (4, 1)')
(4, 1, 'Pour jug 1 to jug 2 (2, 3)')
(2, 3)
4. Write a Python program to implement Uniform Cost Search

Algorithm:

Step 1: Initialize the Priority Queue:

 Use a priority queue to store nodes, where the priority is the


cumulative cost from the start node to the current node.
 Initialize the priority queue with the start node having a cost of 0.
 Use a set or dictionary to track the lowest cost to reach each node.

Step 2: Priority Queue Setup:

 Enqueue the starting node with a priority (cost) of 0.


 Set the cost of the start node to 0.

Step 3 :Uniform Cost Search Algorithm:

 While the priority queue is not empty:


 Dequeue the node with the lowest cumulative cost.
 If the node is the goal, return the path and the cost.
 For each neighbor of the current node:
 Calculate the cost to reach the neighbor through the current node.
 If the calculated cost is lower than the known cost to reach the neighbor (or
the neighbor has not been visited), update the cost and enqueue the neighbor
with the updated cost.
Step 4: Return the Result:

 If the goal node is reached, return the path and the total cost.
 If the priority queue is empty and the goal node has not been reached, return failure (no
path exists).
Program 4:

import heapq
def ucs(graph, start, goal):
queue = [(0, start, [])] # Initialize the priority queue
with the start node

while queue:
cost, node, path = heapq.heappop(queue) # Get the node
with the lowest cost

if node == goal:
return cost, path + [node] # Return the total cost
and path if goal is reached

for neighbor, neighbor_cost in graph[node].items():


heapq.heappush(queue, (cost + neighbor_cost,
neighbor, path + [node]))

# Push neighbor to the priority queue with updated cost


and path
return float("inf"), [] # Return infinity and empty path if
no solution is found

# Example graph represented as an adjacency list


graph = {
'A': {'B': 1, 'C': 2},
'B': {'D': 3},
'C': {'E': 2},
'D': {'F': 4, 'E': 2},
'E': {'D': 4, 'A': 3, 'G': 1},
'F': {'G': 1}, 'G': {}
}

cost, path =ucs(graph, 'A', 'G')

if cost != float("inf"):
print(f"Cost: {cost}")
print(f"Path: {' -> '.join(path)}")
else:
print("Goal node not found")

Output:
5. Write a Python program to implement Depth Limited Search

Algorithm:

Step 1: Initialization:

 Stack: Initialize a stack and push the starting node onto it along with its depth
(usually starting at 0).
 Visited Set: Optionally, create a set to keep track of visited nodes to avoid processing
the same node multiple times within the same depth.

Step 2: Start the Search:

 Pop the top node from the stack along with its depth.
 If this node is the target node, return the path or the node as the solution.
 If the current depth is equal to the depth limit, do not expand this node further.
 Otherwise, explore all adjacent nodes (neighbors) of the popped node.

Step 3: For each neighbor of the current node, if it has not been visited and the depth is within
the limit, mark it as visited (if using a visited set) and push it onto the stack along with its depth
(current depth + 1).

Step 4: Repeat steps 2 and 3 until the stack is empty or the target node is found. Step 5:Return

the Result:

 If the target node is found, return the path or node.


 If the stack becomes empty without finding the target node, return a failure indication
(e.g., "target not found") or "cutoff" if the limit was reached without exploring all
possibilities.
Program 5:

def depth_limited_search(graph, start, goal, depth_limit):


stack = [(start, [start])] # Initialize stack with (node,
path) tuple
visited = set()

while stack:
node, path = stack.pop()

if node == goal:
return path

if len(path) <= depth_limit and node not in visited:


visited.add(node)

for neighbor in graph.get(node, []):


stack.append((neighbor, path + [neighbor]))
return None
if __name__ == "__main__":
# Example graph represented as a dictionary
graph = {
'A': ['B', 'C'],
'B': ['D', 'E'],
'C': ['F'],
'D': [],
'E': ['G', 'H'], 'F': [],
'G': [],
'H': []
}
start_node = 'A'
goal_node = 'H'
depth_limit = 3

path = depth_limited_search(graph, start_node, goal_node,


depth_limit)

if path:
print("Path found:", path)
else:
print(f"No path found within depth limit
{depth_limit}.")

Output
6. Write a Python program to implement Iterative Deepening Depth First Search

Algorithm:

Step 1: Start with an initial depth limit, usually 0. Step 2:

Iterative Deepening:

 Perform a depth-limited search (DLS) with the current depth limit.


 If the DLS finds the target node, return the path or the node as the solution.
 If the DLS does not find the target node and does not reach the depth limit (indicating
the search could go deeper), increase the depth limit by 1 and repeat the process.

Step 3: Repeat
 Continue increasing the depth limit and performing DLS until the target node is
found or there are no more nodes to explore within the new depth limit.
Program 6:

def iterative_deepening_search(start, goal, get_children):


depth = 0

while True:
result, path = depth_limited_search(start, goal, depth, get_children,
[], 0)

if result is not None:


return result, path, depth
depth += 1

def depth_limited_search(node, goal, depth_limit, get_children, path, level):


path.append(node) # Add current node to the path

if node == goal:
return node, path
elif depth_limit == 0:
path.pop() # Remove current node from the path before backtracking
return None, path
else:
for child in get_children(node):
result, child_path = depth_limited_search(child, goal,
depth_limit - 1, get_children, path, level + 1)
if result is not None:
return result, child_path
path.pop() # Remove current node from the path before
backtracking
return None, path

if __name__ == "__main__":
# Define the tree as an adjacency list
tree = {
'A': ['B', 'C'],
'B': ['D', 'E'],
'C': ['F'],
'D': ['G'],
'E': [],
'F': [],
'G': []
}

def get_children(node):
return tree.get(node, [])
# Perform Iterative Deepening Search to find 'G'
start_node = 'A'
goal_node = 'G'
result, path, level = iterative_deepening_search(start_node, goal_node,
get_children)

if result:
print(f"Goal node found: {result}")
print(f"Path: {' -> '.join(path)}")
print(f"Level: {level}")
else:
print("Goal node not found")
Output:
7. Write a Python program to implement A* Search.

Algorithm

Step 1:Initialize:

 Create two lists: openList and closedList.


 Add the start node to openList. The openList is a priority queue where nodes are sorted based on
their f value (where f = g + h).

Step 2: Loop until ‘openList’ is empty:

a. Current Node:

 Remove the node with the lowest f value from openList and call it currentNode.
 Add currentNode to closedList.

b. Goal Test:

 If currentNode is the goal node, construct the path from the start node to the goal node by
tracing back through the parent nodes and return it.

c. Generate Successors:
For each neighbor of currentNode:

i. Skip if in closedList: If the neighbor is in closedList, skip it.

ii. Calculate Scores:

 Set g to be the g value of currentNode plus the cost to move from currentNode
to the neighbor.
 Calculate h, the heuristic estimate of the cost from the neighbor to the goal.
 Calculate f = g + h.

iii. Check Open List:

 If the neighbor is not in openList, add it with the calculated f, g, and h values,
and set its parent to currentNode.
 If the neighbor is in openList but the new g value is lower than the existing g
value, update the neighbor's g value, f value, and parent.

Step 3: If openList is empty and the goal was not reached:

 Return that there is no path.


Program 7:
:
import heapq
def a_star(graph, start, dest, heuristic):
# Initialize distances with infinity and set the start node distance
to 0
distances = {vertex: float('inf') for vertex in graph}
distances[start] = 0
# Initialize parent pointers for path reconstruction
parent = {vertex: None for vertex in graph}
# Priority queue initialized with the start node
pq = [(heuristic[start], start)]
while pq:
curr_f, curr_vert = heapq.heappop(pq)
if curr_vert == dest:
break
for neighbor, weight in graph[curr_vert].items():
tentative_g_score = distances[curr_vert] + weight

if tentative_g_score < distances[neighbor]:


distances[neighbor] = tentative_g_score
f_score = tentative_g_score + heuristic[neighbor]
heapq.heappush(pq, (f_score, neighbor))
parent[neighbor] = curr_vert
return distances, parent
def generate_path_from_parents(parent, start, dest):
path = []
curr = dest
while curr:
path.append(curr)
curr = parent[curr]
path.reverse()
return ' -> '.join(path)

# Define the graph and heuristic


graph = {
'S': {'A': 1, 'G': 10},
'A': {'B': 2, 'C': 1},
'B': {'D': 5},
'C': {'D': 3, 'G': 4},
'D': {'G': 2},
'G': {'G': 0}
}
heuristic = {
'S': 5,
'A': 3,
'B': 4,
'C': 2,
'D': 6,
'G': 0,
}

start = 'S'
dest = 'G'
# Run the A* algorithm
distances, parent = a_star(graph, start, dest, heuristic)
path = generate_path_from_parents(parent, start, dest)
# Calculate total path cost
total_cost = distances[dest]

print('Distances:', distances)
print('Parents:', parent)
print('Optimal Path:', path)
print('Total Path Cost:', total_cost)

Output:

Distances: {'S': 0, 'A': 1, 'B': 3, 'C': 2, 'D': 5, 'G': 6}


Parents: {'S': None, 'A': 'S', 'B': 'A', 'C': 'A', 'D': 'C', 'G': 'C'}
Optimal Path: S -> A -> C -> G
Total Path Cost: 6
8. Write a Python Program to implement Min Max algorithm.

Algorithm:

Step 1: Define the Game Tree:

 Each node represents a game state.


 The root node represents the current game state.

 The children of a node represent possible game states resulting from the next move.

Step 2: Scoring:

 Each leaf node (end game state) has a score representing the outcome of the game from the
perspective of the maximizing player.
 Positive scores indicate a win for the maximizing player, negative scores indicate a win for
the minimizing player, and zero indicates a draw.

Step 3: Recursive Evaluation:

 Maximizing Player: Chooses the move with the maximum score.


 Minimizing Player: Chooses the move with the minimum score.

Step 4: Base Case: If the game is over (leaf node), return the score of the game state.

Step 5: Recursive Case: Evaluate the scores of the child nodes and choose the optimal score for the
current player.
Program 8:

import math
def minimax(curDepth, nodeIndex, maxTurn, scores, targetDepth):
# Base case: targetDepth reached
if curDepth == targetDepth:
return scores[nodeIndex]

if maxTurn:
return max(
minimax(curDepth + 1, nodeIndex * 2, False, scores,
targetDepth),
minimax(curDepth + 1, nodeIndex * 2 + 1, False, scores,
targetDepth)
)
else:
return min(
minimax(curDepth + 1, nodeIndex * 2, True, scores,
targetDepth),
minimax(curDepth + 1, nodeIndex * 2 + 1, True, scores,
targetDepth)
)
scores = [-1, 4, 2, 6, -3, -5, 0, 7]
treeDepth = math.log(len(scores), 2)

print("The optimal value is :", end=" ")


print(minimax(0, 0, True, scores, int(treeDepth)))

Output:
The optimal value is : 4
9. Write a Program to implement Alpha-Beta Pruning using Python.

Algorithm:

Step 1 :Initialize Alpha and Beta:

 Alpha: The best value that the maximizing player can guarantee at that level or above.
 Beta: The best value that the minimizing player can guarantee at that level or above.

Step 2: Recursive Evaluation with Pruning:

 At each node, the algorithm keeps track of the two values, alpha and beta, which represent the
minimum score that the maximizing player is assured and the maximum score that the minimizing
player is assured, respectively.
 These values are updated as the algorithm progresses and are used to prune branches that cannot
affect the final decision.

Step 3: Prune Branches:

 If at any point the algorithm finds a move that proves to be worse than a previously examined
move, it stops evaluating that branch (prunes it).

Step 4: Base Case:

 If the game is over (leaf node), return the score of the game state.

Step 5: Recursive Case:

 Evaluate the scores of the child nodes while updating alpha and beta, and prune branches
when possible.
Program 9:

MAX, MIN = 1000, -1000


def minimax(depth, nodeIndex, maximizingPlayer, values, alpha,
beta):
if depth == 3:
return values[nodeIndex]

if maximizingPlayer:
best = MIN
for i in range(0, 2):
val = minimax(depth + 1, nodeIndex * 2 + i,
False, values, alpha, beta)
best = max(best, val)
alpha = max(alpha, best)
if beta <= alpha:
break
return best
else:
best = MAX
for i in range(0, 2):
val = minimax(depth + 1, nodeIndex * 2 + i,
True, values, alpha, beta)
best = min(best, val)
beta = min(beta, best)
if beta <= alpha:
break
return best

if __name__ == "__main__":
values = []
num_nodes = int(input("Enter the number of nodes: "))
for i in range(num_nodes):
value = int(input(f"Enter value for node {i}: "))
values.append(value)
print("The optimal value is:", minimax(0, 0, True, values,
MIN, MAX))
Output:
Enter the number of nodes: 8
Enter value for node 0: -1
Enter value for node 1: 4
Enter value for node 2: 2
Enter value for node 3: 6
Enter value for node 4: -3
Enter value for node 5: -5
Enter value for node 6: 0
Enter value for node 7: 7
The optimal value is: 4

You might also like