0% found this document useful (0 votes)
11 views

Lab3 - Uninformed Search Algorithms (BFS)

Uploaded by

alomisimunif9
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)
11 views

Lab3 - Uninformed Search Algorithms (BFS)

Uploaded by

alomisimunif9
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/ 8

Understanding Uninformed Search Algorithms

Uninformed search algorithms, also known as blind search algorithms, are a


class of search algorithms used in artificial intelligence that do not use any
domain-specific knowledge about the problem being solved. These algorithms
rely solely on the information provided in the problem definition, such as the
initial state, actions available in each state, and the goal state. Uninformed
search algorithms are called "blind" because they do not have a heuristic
function to guide the search towards the goal; instead, they explore the search
space systematically.
Uninformed search algorithms play a fundamental role in artificial intelligence
(AI) by providing basic search strategies for exploring problem spaces where no
additional knowledge is available beyond the problem definition. These
algorithms are crucial for solving a wide range of problems in AI, such as
pathfinding, puzzle solving, and state-space search.

The main role of uninformed search algorithms is to systematically explore the


search space to find a solution, without using any domain-specific knowledge or
heuristics. While these algorithms may not always be the most efficient, they
provide a baseline for understanding and solving complex problems in AI.
1. Breadth-First Search (BFS)
Breadth-First Search is one of the simplest and most fundamental search
algorithms in AI. It starts at the initial state and explores all the neighbor nodes
at the present depth prior to moving on to the nodes at the next depth level.
BFS uses a queue data structure to keep track of the nodes that are yet to be
explored. It guarantees the shortest path from the initial state to the goal state
if the path cost is a non-decreasing function of the depth of the node.
We could implement breadth-first search as a call to BEST-FIRST-SEARCH where
the evaluation function f(n) is the depth of the node—that is, the number of
actions it takes to reach the node.

1. Romania Cities Example:


from collections import deque

# Define the graph for the Romania problem


romania_map = {
"Arad": ["Zerind", "Sibiu", "Timisoara"],
"Zerind": ["Arad", "Oradea"],
"Oradea": ["Zerind", "Sibiu"],
"Sibiu": ["Arad", "Oradea", "Fagaras", "Rimnicu Vilcea"],
"Timisoara": ["Arad", "Lugoj"],
"Lugoj": ["Timisoara", "Mehadia"],
"Mehadia": ["Lugoj", "Drobeta"],
"Drobeta": ["Mehadia", "Craiova"],
"Craiova": ["Drobeta", "Rimnicu Vilcea", "Pitesti"],
"Rimnicu Vilcea": ["Sibiu", "Craiova", "Pitesti"],
"Fagaras": ["Sibiu", "Bucharest"],
"Pitesti": ["Rimnicu Vilcea", "Craiova", "Bucharest"],
"Bucharest": ["Fagaras", "Pitesti", "Giurgiu", "Urziceni"],
"Giurgiu": ["Bucharest"],
"Urziceni": ["Bucharest", "Hirsova", "Vaslui"],
"Hirsova": ["Urziceni", "Eforie"],
"Eforie": ["Hirsova"],
"Vaslui": ["Urziceni", "Iasi"],
"Iasi": ["Vaslui", "Neamt"],
"Neamt": ["Iasi"]
}

# BFS implementation
def bfs(start, goal):
# Queue to maintain paths
queue = deque([[start]])
visited = set() # Set to track visited nodes

while queue:
path = queue.popleft() # Get the first path from the
queue
node = path[-1] # Get the last node in the path

# If the goal is reached, return the path


if node == goal:
return path

# If the node hasn't been visited, add neighbors to the


queue
if node not in visited:
visited.add(node)
for neighbor in romania_map.get(node, []):
new_path = list(path)
new_path.append(neighbor)
queue.append(new_path)

return None # Return None if no path is found

# Test the BFS function


start_city = "Arad"
goal_city = "Bucharest"
path = bfs(start_city, goal_city)
if path:
print(f"Path from {start_city} to {goal_city}: {' ->
'.join(path)}")
else:
print(f"No path found from {start_city} to {goal_city}.")

2. Missionaries and Cannibals Example:

from collections import deque

def is_valid_state(state):
"""Check if a state is valid."""
m_left, c_left, m_right, c_right, _ = state

# Validate side conditions


if m_left < 0 or c_left < 0 or m_right < 0 or c_right < 0:
return False
if (m_left > 0 and m_left < c_left) or (m_right > 0 and
m_right < c_right):
return False
return True

def get_successors(state):
"""Generate all possible valid successors from the current
state."""
m_left, c_left, m_right, c_right, boat = state
successors = []
moves = [(1, 0), (0, 1), (1, 1), (2, 0), (0, 2)] # Possible
movements

for move in moves:


if boat == 1: # Boat on the left side
new_state = (
m_left - move[0], c_left - move[1], # Left side
changes
m_right + move[0], c_right + move[1], # Right
side changes
0 # Boat moves to the right
)
else: # Boat on the right side
new_state = (
m_left + move[0], c_left + move[1], # Left side
changes
m_right - move[0], c_right - move[1], # Right
side changes
1 # Boat moves to the left
)
if is_valid_state(new_state):
successors.append(new_state)
return successors

def bfs(initial_state, goal_state):


"""Solve the problem using BFS."""
queue = deque([(initial_state, [])]) # (state, path)
visited = set()

while queue:
current_state, path = queue.popleft()

if current_state in visited:
continue

visited.add(current_state)
path = path + [current_state]

# Check if we reached the goal


if current_state[:4] == goal_state[:4]: # Compare only
state values, ignore boat position
return path

# Explore successors
for successor in get_successors(current_state):
if successor not in visited:
queue.append((successor, path))
return None

# Define initial and goal states


initial_state = (
3, 3, 0, 0, 1) # (missionaries_left, cannibals_left,
missionaries_right, cannibals_right, boat_position)
goal_state = (0, 0, 3, 3, 0) # Same structure

# Solve the problem


solution = bfs(initial_state, goal_state)

# Print the solution


if solution:
print("Solution found:")
for step in solution:
print(step)
else:
print("No solution found.")

Python Deque:

Deque (Doubly Ended Queue) in Python is implemented using the module


“collections“. Deque is preferred over a list in the cases where we need quicker
append and pop operations from both the ends of the container, as deque
provides an O(1) time complexity for append and pop operations as compared
to a list that provides O(n) time complexity.

Another BFS Example, Maze Example:


import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
from collections import deque

def bfs(maze, start, goal):


queue = deque([(start, [])])
visited = set()

while queue:
current, path = queue.popleft()
x, y = current

if current == goal:
return path + [current]

if current in visited:
continue

visited.add(current)

for dx, dy in [(0, 1), (0, -1), (1, 0), (-1, 0)]:
nx, ny = x + dx, y + dy
if 0 <= nx < len(maze) and 0 <= ny < len(maze[0])
and maze[nx][ny] != 1:
queue.append(((nx, ny), path + [current]))

return None

def visualize_maze(maze, start, goal, path=None):


cmap = ListedColormap(['white', 'black', 'red', 'blue',
'green'])
bounds = [0, 0.5, 1.5, 2.5, 3.5, 4.5]
norm = plt.Normalize(bounds[0], bounds[-1])

fig, ax = plt.subplots()
ax.imshow(maze, cmap=cmap, norm=norm)

ax.scatter(start[1], start[0], color='yellow', marker='o',


label='Start')
ax.scatter(goal[1], goal[0], color='purple', marker='o',
label='Goal')

if path:
for node in path[1:-1]:
ax.scatter(node[1], node[0], color='green',
marker='o')
ax.legend()
plt.show()

# Example maze
maze = np.array([
[0, 0, 0, 0, 0],
[1, 1, 0, 1, 1],
[0, 0, 0, 0, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 0]
])

start = (0, 0)
goal = (4, 4)

path = bfs(maze, start, goal)

visualize_maze(maze, start, goal, path)

You might also like