Ai 1
Ai 1
Program:
from collections import deque, defaultdict
class Graph:
def __init__(self):
self.graph = defaultdict(list)
g.dfs(start)
print("\nBFS Traversal:")
g.bfs(start)
Output:
Enter number of edges: 4
Enter edge (u v): 0 1
Enter edge (u v): 0 4
Enter edge (u v): 1 3
Enter edge (u v): 2 6
Enter starting node: 1
DFS Traversal:
13
BFS Traversal:
13
Result:
1. Depth First Search (DFS)
Completeness: ❌ Not complete (fails in infinite-depth graphs). ✅ Complete in finite graphs.
Optimality: ❌ Not optimal (may find longer path first).
Time Complexity: O(bm)O(b^m)O(bm) (b = branching factor, m = maximum depth).
Space Complexity: O(bm)O(bm)O(bm) (linear in depth).
2. Breadth First Search (BFS)
Completeness: ✅ Complete (if branching factor is finite).
Optimality: ✅ Optimal for unweighted graphs.
Time Complexity: O(bd)O(b^d)O(bd) (b = branching factor, d = depth of shallowest goal).
Space Complexity: O(bd)O(b^d)O(bd) (stores all nodes in frontier).
Hence, the program to implement BFS and DFS has been executed successfully.
Aim: To write a Program to find the solution for traveling salesman Problem.
Description:
Traveling Salesman Problem (TSP)
A combinatorial optimization problem.
Problem: A salesman must visit each city once and return to the starting city with minimum cost.
Nature: NP-hard problem.
Approaches:
o Brute force (check all permutations).
o Dynamic programming (Held-Karp algorithm).
o Approximation algorithms (nearest neighbor, branch and bound).
Applications: Logistics, manufacturing, microchip design.
Program:
import itertools
def tsp(graph, start):
vertices = list(range(len(graph)))
vertices.remove(start)
min_path = float('inf')
best_route = []
for perm in itertools.permutations(vertices):
cost = 0
k = start
for j in perm:
cost += graph[k][j]
k=j
cost += graph[k][start]
if cost < min_path:
min_path = cost
best_route = [start] + list(perm) + [start]
return best_route, min_path
n = int(input("Enter number of cities: "))
graph = []
print("Enter cost matrix (space-separated):")
for i in range(n):
row = list(map(int, input(f"Row {i+1}: ").split()))
graph.append(row)
start = int(input("Enter starting city (0-indexed): "))
route, cost = tsp(graph, start)
print(f"\nBest Route: {route}")
print(f"Minimum Cost: {cost}")
Output:
Enter number of cities: 4
Enter cost matrix (space-separated):
Row 1: 0 15 10 20
Row 2: 25 0 10 15
Row 3: 35 30 0 10
Row 4: 10 20 25 0
Enter starting city (0-indexed): 0
Result:
Hence, the program to implement the travelling sales person problem has been executed successfully.
Program:
import math
import random
def objective_function(x):
return x**2+ 10 * math.sin(x)
def simulated_annealing(start, temp, cooling):
current = start
T = temp
while T > 0.1:
new = current + random.uniform(-1, 1)
delta = objective_function(new) - objective_function(current)
if delta < 0 or math.exp(-delta / T) > random.random():
current = new
T *= cooling
return current, objective_function(current)
start = float(input("Enter starting point: "))
temp = float(input("Enter initial temperature (e.g., 1000): "))
cooling = float(input("Enter cooling rate (e.g., 0.95): "))
result, value = simulated_annealing(start, temp, cooling)
print(f"\nApproximate Minimum at x = {result:.4f}, f(x) = {value:.4f}")
output:
Enter starting point: 0
Enter initial temperature (e.g., 1000): 3000
Enter cooling rate (e.g., 0.95): 0.7
Result:
Completeness: ❌ Not complete (may fail if cooling schedule is poor).
Optimality: ❌ Not optimal (gives near-optimal solutions).
Time Complexity: Depends on schedule; often O(k)O(k)O(k), where kkk is iterations.
Space Complexity: O(1)O(1)O(1).
Hence, the program to implement Simulated Annealing Algorithm has been executed successfully.
Aim: To write a program to find the solution for the wumpus world problem.
Description:
Wumpus World Problem
A knowledge-based AI environment.
Environment: A grid cave with pits, a Wumpus (monster), and gold.
Percepts:
o Breeze → pit nearby.
o Stench → Wumpus nearby.
o Glitter → gold in current cell.
Goal: Agent must grab gold and exit safely without falling into pit or being eaten.
AI Concept: Uses logical reasoning (propositional logic, inference rules) to act safely.
Program:
class WumpusWorld:
def __init__(self, grid, agent_pos):
self.grid = grid
self.rows = len(grid)
self.cols = len(grid[0])
self.agent_pos = agent_pos
def perceive(self, pos):
x, y = pos
tile = self.grid[x][y]
if tile == 'W':
return "Wumpus! Danger!"
elif tile == 'P':
return "Pit! Danger!"
elif tile == 'G':
return "Gold! You win!"
else:
return "Safe"
def move_agent(self, new_pos):
self.agent_pos = new_pos
return self.perceive(new_pos)
rows = int(input("Enter number of rows: "))
cols = int(input("Enter number of columns: "))
print(f"\nEnter the {rows}x{cols} Wumpus World grid row by row.")
print("Use symbols: '.' = empty, 'W' = Wumpus, 'P' = Pit, 'G' = Gold, 'A' = Agent")
grid = []
agent_pos = None
for i in range(rows):
while True:
row = input(f"Row {i+1} (space separated): ").split()
if len(row) != cols:
print(f"Please enter exactly {cols} values.")
continue
grid.append(row)
for j, val in enumerate(row):
if val == 'A':
agent_pos = (i, j)
break
if agent_pos is None:
print("No agent 'A' found! Please restart and enter again.")
exit()
world = WumpusWorld(grid, agent_pos)
print("\nEnter moves in format 'x y' (0-based index). Type 'exit' to quit.")
while True:
move = input("Move to: ")
if move.lower() == 'exit':
break
try:
x, y = map(int, move.split())
if 0 <= x < rows and 0 <= y < cols:
print(f"Perception: {world.move_agent((x, y))}")
else:
print("Invalid move. Out of bounds.")
except:
print("Invalid format. Enter as: x y")
Output:
Enter number of rows: 3
Enter number of columns: 3
Enter moves in format 'x y' (0-based index). Type 'exit' to quit.
Move to: 1 2
Perception: Safe
Move to: 2 2
Perception: Gold! You win!
Move to: exit
Result:
Completeness: ✅ Complete (if reasoning rules cover all cases).
Optimality: ✅ Optimal if logical inference is correct.
Time Complexity: Exponential in worst case (logical inference = NP-hard).
Space Complexity: Exponential (needs to store knowledge base).
Hence, the program to find the solution for the wumpus world problem has been executed successfully.
Program:
def print_board(solution, N):
board = []
for row in range(N):
line = ""
for col in range(N):
if solution[row] == col:
line += "Q "
else:
line += ". "
board.append(line.strip())
return "\n".join(board)
def is_safe(position, row, col):
for i in range(row):
if position[i] == col or \
position[i] - i == col - row or \
position[i] + i == col + row:
return False
return True
def solve_n_queens(N):
solutions = []
def backtrack(row, position):
if row == N:
solutions.append(position[:])
return
for col in range(N):
if is_safe(position, row, col):
position[row] = col
backtrack(row + 1, position)
position = [-1] * N
backtrack(0, position)
return solutions
N = int(input("Enter the number of queens (N): "))
solutions = solve_n_queens(N)
print(f"\nTotal solutions for {N}-Queens: {len(solutions)}\n")
Output:
Enter the number of queens (N): 4
Total solutions for 4-Queens: 2
Solution 1:
.Q..
...Q
Q...
..Q.
Solution 2:
..Q.
Q...
...Q
.Q..
Result:
Completeness: ✅ Complete (backtracking always finds solution).
Optimality: ✅ Optimal (finds valid arrangement).
Time Complexity: O(N!)O(N!)O(N!) for brute force; backtracking reduces it but still exponential.
Space Complexity: O(N)O(N)O(N) (stack depth for recursion).
Hence, the program to implement N-Queen problem has been executed successfully.
Program:
import heapq
GOAL = [[1, 2, 3],
[4, 5, 6],
[7, 8, 0]]
MOVES = [("Up", -1, 0), ("Down", 1, 0), ("Left", 0, -1), ("Right", 0, 1)]
def manhattan_distance(state):
distance = 0
for i in range(3):
for j in range(3):
val = state[i][j]
if val != 0: # Ignore blank
goal_x, goal_y = divmod(val - 1, 3)
distance += abs(goal_x - i) + abs(goal_y - j)
return distance
def get_blank_position(state):
for i in range(3):
for j in range(3):
if state[i][j] == 0:
return i, j
def is_goal(state):
return state == GOAL
def state_to_tuple(state):
return tuple(tuple(row) for row in state)
def clone_state(state):
return [row[:] for row in state]
def print_state(state):
for row in state:
print(" ".join(str(x) if x != 0 else "." for x in row))
print()
def make_move(state, move):
x, y = get_blank_position(state)
dx, dy = 0, 0
for name, mx, my in MOVES:
if name == move:
dx, dy = mx, my
break
new_x, new_y = x + dx, y + dy
new_state = clone_state(state)
new_state[x][y], new_state[new_x][new_y] = new_state[new_x][new_y], new_state[x][y]
return new_state
def is_solvable(state):
Output:
Enter the initial 8-puzzle state row by row (use 0 for blank):
123
456
780
Initial State:
123
456
78.
Solution Path:
123
456
78.
Result:
Completeness: ✅ Complete with BFS, A*.
Optimality: ✅ Optimal with A* (admissible heuristic), ❌ not with DFS.
Time Complexity:
o BFS: O(bd)O(b^d)O(bd).
o A*: O(bd)O(b^d)O(bd), but faster with heuristics.
Space Complexity:
o BFS: O(bd)O(b^d)O(bd).
o A*: O(bd)O(b^d)O(bd).
Hence, the program to implement 8 puzzle problem has been executed successfully.
Program:
def towers_of_hanoi(n, source, auxiliary, destination):
if n == 1:
print(f"Move disk 1 from {source} to {destination}")
return
towers_of_hanoi(n - 1, source, destination, auxiliary)
print(f"Move disk {n} from {source} to {destination}")
towers_of_hanoi(n - 1, auxiliary, source, destination)
try:
n = int(input("Enter number of disks: "))
print(f"\nTowers of Hanoi solution for {n} disks:\n")
towers_of_hanoi(n, 'A', 'B', 'C')
print(f"\nTotal moves required: {2**n - 1}")
except ValueError:
print("Please enter a valid integer.")
Output:
Enter number of disks: 3
Towers of Hanoi solution for 3 disks:
Move disk 1 from A to C
Move disk 2 from A to B
Move disk 1 from C to B
Move disk 3 from A to C
Move disk 1 from B to A
Move disk 2 from B to C
Move disk 1 from A to C
Result:
Completeness: ✅ Complete.
Optimality: ✅ Optimal (always solves in minimum moves).
Time Complexity: O(2n)O(2^n)O(2n).
Space Complexity: O(n)O(n)O(n) (recursive call stack).
Hence, the program to implement Towers of Hanoi problem has been executed successfully.
Program:
import heapq
def best_first_search(graph, heuristics, start, goal):
pq = []
heapq.heappush(pq, (heuristics[start], start)) # (heuristic, node)
visited = set()
while pq:
_, current = heapq.heappop(pq)
if current in visited:
continue
print(current, end=" -> ")
visited.add(current)
if current == goal:
print("Goal reached!")
return
for neighbor in graph.get(current, []):
if neighbor not in visited:
heapq.heappush(pq, (heuristics[neighbor], neighbor))
print("Goal not found!")
graph = {}
n = int(input("Enter number of nodes: ").strip())
for _ in range(n):
node = input("Node name: ").strip()
neighbors_input = input(f"Neighbors of {node} (comma-separated, leave blank if none): ").strip()
if neighbors_input:
graph[node] = [x.strip() for x in neighbors_input.split(",")]
else:
graph[node] = []
heuristics = {}
for node in graph:
heuristics[node] = int(input(f"Heuristic for {node}: ").strip())
start_node = input("Enter start node: ").strip()
Output:
Enter number of nodes: 2
Node name: S
Neighbors of S (comma-separated, leave blank if none): A
Node name: A
Neighbors of A (comma-separated, leave blank if none):
Heuristic for S: 1
Heuristic for A: 0
Enter start node: S
Enter goal node: A
Result:
Completeness: ❌ Not complete (can get stuck in loops). ✅ Complete if cycle-checking is added.
Optimality: ❌ Not optimal (greedy by h(n)).
Time Complexity: O(bm)O(b^m)O(bm) (m = maximum depth).
Space Complexity: O(bm)O(b^m)O(bm).
Hence, the program to implement best first search has been executed successfully.
Program:
from queue import PriorityQueue
def astar_search(graph, heuristics, start, goal):
open_set = PriorityQueue()
open_set.put((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] = heuristics[start]
while not open_set.empty():
_, current = open_set.get()
if current == goal:
return reconstruct_path(came_from, current)
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 + heuristics[neighbor]
open_set.put((f_score[neighbor], neighbor))
return None
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 = {}
n = int(input("Enter number of nodes: "))
print("Enter graph edges and costs (format: neighbor cost), type 'done' to stop for a node.")
for i in range(n):
node = input(f"Enter node name {i+1}: ")
graph[node] = []
while True:
edge = input(f" Neighbor and cost from {node} (or 'done'): ")
if edge.lower() == "done":
break
neighbor, cost = edge.split()
graph[node].append((neighbor, int(cost)))
heuristics = {}
print("\nEnter heuristic values for each node:")
for node in graph:
heuristics[node] = int(input(f"Heuristic for {node}: "))
start = input("\nEnter start node: ")
goal = input("Enter goal node: ")
path = astar_search(graph, heuristics, start, goal)
print("\n--- A* Search Result ---")
if path:
print("Path found:", " -> ".join(path))
else:
print("No path found!")
Output:
Enter number of nodes: 3
Enter graph edges and costs (format: neighbor cost), type 'done' to stop for a node.
Enter node name 1: S
Neighbor and cost from S (or 'done'): A 1
Neighbor and cost from S (or 'done'): B 4
Neighbor and cost from S (or 'done'): done
Enter node name 2: A
Neighbor and cost from A (or 'done'): done
Enter node name 3: B
Neighbor and cost from B (or 'done'): done
Result:
Completeness: ✅ Complete (if branching factor finite and h(n) admissible).
Optimality: ✅ Optimal with admissible heuristic.
Time Complexity: O(bd)O(b^d)O(bd) worst case.
Space Complexity: O(bd)O(b^d)O(bd) (keeps all generated nodes in memory).
Program:
class Graph:
def __init__(self, graph, heuristics, start):
self.graph = graph
self.H = heuristics
self.start = start
self.status = {}
self.solution_graph = {}
def get_neighbors(self, node):
return self.graph.get(node, [])
def get_status(self, node):
return self.status.get(node, 0)
def set_status(self, node, value):
self.status[node] = value
def ao_star(self, node):
print(f"Expanding node: {node}")
if self.get_status(node) == -1:
return
neighbors = self.get_neighbors(node)
if not neighbors:
self.set_status(node, -1)
return
min_cost = float('inf')
best_path = None
for option in neighbors:
cost = 0
for child in option:
cost += self.H[child] + 1 # edge cost = 1
if cost < min_cost:
min_cost = cost
best_path = option
self.H[node] = min_cost
self.solution_graph[node] = best_path
solved = True
for child in best_path:
if self.get_status(child) != -1:
solved = False
break
if solved:
self.set_status(node, -1)
Output:
Enter number of nodes: 4
Enter node name 1: S
Enter AND/OR option for S (space separated nodes, or 'done'): AB
Enter AND/OR option for S (space separated nodes, or 'done'): done
Enter node name 2: A
Enter AND/OR option for A (space separated nodes, or 'done'): C
Enter AND/OR option for A (space separated nodes, or 'done'): done
Enter node name 3: B
Enter AND/OR option for B (space separated nodes, or 'done'): done
Enter node name 4: C
Enter AND/OR option for C (space separated nodes, or 'done'): done
Result:
Completeness: ✅ Complete (for AND-OR graphs if heuristics consistent).
Optimality: ✅ Optimal with admissible heuristics.
Time Complexity: Exponential in worst case.
Space Complexity: Exponential (stores graph + partial expansions).
Hence, the program to implement AO* algorithm has been executed successfully.