Lab Problem 01 AI
Lab Problem 01 AI
Args:
graph: A dictionary where keys are nodes and values are lists of
neighbors.
start: The starting node for the traversal.
Returns:
A list containing the nodes visited in BFS order.
"""
visited = set() # Keep track of visited nodes
queue = deque([start]) # Queue for BFS traversal
while queue:
current_node = queue.popleft()
visited.add(current_node)
return visited
# Example usage
graph = {
0: [1, 2],
1: [2, 3],
2: [0, 1, 4],
3: [1],
4: [2]
}
starting_node = 0
bfs_result = bfs(graph, starting_node)
print(f"BFS traversal starting from {starting_node}: {bfs_result}")
This code defines a function bfs that takes a graph (represented as an
adjacency list) and a starting node as input. It uses two data structures:
visited: A set to keep track of nodes that have already been visited
during the traversal.
queue: A deque (double-ended queue) to implement the BFS behavior.
Nodes are added to the back of the queue and removed from the front.
The algorithm works as follows:
1. Initialize visited and queue with the starting node added to the queue.
2. While the queue is not empty:
o Dequeue a node from the queue and mark it as visited.
o For each unvisited neighbor of the dequeued node:
Add the neighbor to the back of the queue.
Mark the neighbor as visited.
3. Return the set of visited nodes.
The example usage demonstrates how to use the bfs function with a sample
graph and starting node. It then prints the BFS traversal order.
Uniform-Cost Search
class Node:
def __init__(self, state, parent=None, cost=0):
self.state = state
self.parent = parent
self.cost = cost
Args:
graph: A dictionary representing the graph. Keys are nodes, values are
dictionaries where keys are neighbors and values are edge costs.
start: The starting node.
goal: The goal node.
Returns:
A list of nodes representing the path from start to goal, or None if no
path is found.
"""
frontier = []
explored = set()
heappush(frontier, Node(start))
while frontier:
current = heappop(frontier)
explored.add(current.state)
if current.state == goal:
# Found the goal! Reconstruct the path
path = []
while current:
path.append(current.state)
current = current.parent
return path[::-1]
# Example usage
graph = {
'A': {'B': 1, 'C': 3},
'B': {'D': 2, 'E': 4},
'C': {'D': 5},
'D': {'E': 1},
'E': {}
}
start = 'A'
goal = 'E'
if path:
print("Path found:", path)
else:
print("No path found")
This code defines a Node class to represent nodes in the graph. The
uniform_cost_search function takes a graph, starting node, and goal node as
input. It uses a priority queue (implemented with heapq) to explore nodes
with the lowest cost first. The function returns a list of nodes representing
the path from start to goal, or None if no path is found.
This is a basic implementation and can be extended to include features like:
Handling different data structures for representing graphs (e.g.,
adjacency lists)
Heuristic functions to guide the search towards the goal
Error handling for invalid inputs
Args:
graph: A dictionary representing the graph. Keys are nodes, values are
lists of neighbors.
start: The starting node.
visited: An optional set to track visited nodes (default None).
Returns:
A list of nodes visited in the DFS traversal.
"""
if visited is None:
visited = set() # Initialize visited set if not provided
visited.add(start)
print(start, end=" ") # You can modify this line to perform actions on
visited nodes
# Example usage
graph = {
'A': ['B', 'C'],
'B': ['D', 'E'],
'C': ['F'],
'D': [],
'E': ['F'],
'F': []
}
start = 'A'
Args:
graph: A dictionary representing the graph. Keys are nodes, values are
lists of neighbors.
start: The starting node.
goal: The goal node.
depth: The maximum depth limit for the search.
Returns:
"cutoff" if the depth limit is reached before finding the goal.
"failure" if the goal is not found within the depth limit.
A list of nodes representing the path to the goal if found, otherwise None.
"""
def recursive_dls(node, current_depth):
if current_depth == depth:
return "cutoff" # Reached depth limit
if node == goal:
return [node] # Found the goal!
result = recursive_dls(start, 0)
if result == "cutoff":
return "cutoff"
elif result == "failure":
return "failure"
else:
return result # Return the path
# Example usage
graph = {
'A': ['B', 'C'],
'B': ['D', 'E'],
'C': ['F'],
'D': [],
'E': ['F'],
'F': ['G']
}
start = 'A'
goal = 'G'
depth = 3
print(result)
This code defines two functions:
depth_limited_search: This function takes the graph, starting node,
goal node, and depth limit as input. It calls the helper function
recursive_dls to perform the actual search.
recursive_dls: This recursive function explores the graph up to the
specified depth. It returns "cutoff" if the depth limit is reached, "failure"
if the goal is not found within the limit, or a list of nodes representing
the path to the goal if found.
The code uses a copy of the graph during DLS to avoid modifying the original
graph in the example.
This implementation allows you to iteratively increase the depth limit until
the goal is found or all possibilities are exhausted.
Args:
graph: A dictionary representing the graph. Keys are nodes, values are
lists of neighbors.
start: The starting node.
goal: The goal node.
Returns:
A list of nodes representing the path to the goal if found, otherwise None.
"""
for depth in range(1, float('inf')):
if depth_first_search_limited(graph, start, goal, depth):
return depth_first_search_limited(graph, start, goal, depth)
Args:
graph: A dictionary representing the graph. Keys are nodes, values are
lists of neighbors.
start: The starting node.
goal: The goal node.
depth: The maximum depth limit for the search.
Returns:
A list of nodes representing the path to the goal if found, otherwise None.
"""
stack = [(start, 0)]
visited = set()
while stack:
current, current_depth = stack.pop()
if current_depth > depth:
continue
if current == goal:
path = []
while stack:
node, _ = stack.pop()
path.append(node)
return path[::-1] # Reverse path to get start to goal order
# Example usage
graph = {
'A': ['B', 'C'],
'B': ['D', 'E'],
'C': ['F'],
'D': [],
'E': ['F'],
'F': ['G']
}
start = 'A'
goal = 'G'
Bidirectional Search
Here's a Python implementation of Bidirectional Search:
Python
from collections import deque
Args:
graph: A dictionary representing the graph. Keys are nodes, values are
dictionaries where keys are neighbors and values are edge costs (assumed
to be 1 in this implementation).
start: The starting node.
goal: The goal node.
Returns:
A list of nodes representing the path from start to goal if found,
otherwise None.
"""
forward_queue = deque([start])
forward_visited = set([start])
backward_queue = deque([goal])
backward_visited = set([goal])
Args:
node1: Node from the forward search.
meeting_point: The node where the two searches met.
visited1: Set of visited nodes from the forward search.
visited2: Set of visited nodes from the backward search.
Returns:
A list of nodes representing the path from start to goal.
"""
path1 = []
while node1:
path1.append(node1)
node1 = get_parent(node1, visited1)
path2 = list(reversed([node for node in get_parents(meeting_point,
visited2)]))
return path1 + path2
Args:
node: The current node.
visited: Set of visited nodes with parent information (modified for this
function).
Returns:
The parent node of the current node, or None if not found.
"""
for parent, child in visited:
if child == node:
return parent
return None
Args:
node: The current node.
visited: Set of visited nodes with parent information (modified for this
function).
Returns:
A list of parent nodes of the current node, or an empty list if not found.
"""
parents = []
for parent, child in visited:
if child == node:
parents.append(parent)
return parents
# Example usage
graph = {
'A': {'B': 1, 'C': 1},
'B': {'D': 1, 'E': 1},
'C': {'D': 1},
'D': {'E': 1},
'E': {}
}
start = 'A'
goal = 'E'
path = bidirectional_search(graph, start, goal)
if path:
print("Path found:", path)
else:
print("No path found")
This implementation uses two frontiers, one for the forward search starting
from the beginning and another for the backward search starting from the
goal. It explores both frontiers simultaneously until they meet at a common
node. The reconstruct_path function then backtracks from the meeting point
to build the complete path from start to goal.
Note:
This implementation assumes edge costs are all 1. You can modify it to
handle different costs.
The get_parent and get_parents functions are helper functions to
retrieve parent information from the visited sets, which are
class Node:
def __init__(self, state, parent=None, heuristic_value=float('inf')):
self.state = state
self.parent = parent
self.heuristic_value = heuristic_value
Args:
graph: A dictionary representing the graph. Keys are nodes, values are
dictionaries where keys are neighbors and values are edge costs.
start: The starting node.
goal: The goal node.
heuristic_fn: A function that takes a state and returns its heuristic value
(estimated distance to goal).
Returns:
A list of nodes representing the path from start to goal if found,
otherwise None.
"""
frontier = []
explored = set()
heappush(frontier, Node(start, heuristic_value=heuristic_fn(start, goal)))
while frontier:
current = heappop(frontier)
explored.add(current.state)
if current.state == goal:
# Found the goal! Reconstruct the path
path = []
while current:
path.append(current.state)
current = current.parent
return path[::-1]
graph = {
'A': {'B': 1, 'C': 3},
'B': {'D': 2, 'E': 4},
'C': {'D': 5},
'D': {'E': 1},
'E': {}
}
start = 'A'
goal = 'E'
if path:
print("Path found:", path)
else:
print("No path found")
This code defines a Node class to represent nodes in the graph with an
additional attribute for heuristic value. The greedy_best_first_search function
works similarly to Uniform Cost Search (UCS) but uses a priority queue to
prioritize nodes with lower heuristic values. The heuristic_fn argument is a
user-defined function that estimates the distance from a state to the goal.
Note:
This example uses a simple distance heuristic. You'll need to replace it
with a more specific heuristic function depending on your problem
domain.
The code uses a copy of the graph to avoid modifying the original
graph during the search.
A* Search
Here's a Python implementation of A* Search:
Python
from heapq import heappush, heappop
class Node:
def __init__(self, state, parent=None, g_score=0, h_score=0):
self.state = state
self.parent = parent
self.g_score = g_score # Cost from start to current node
self.h_score = h_score # Heuristic estimate of cost to goal
self.f_score = g_score + h_score # Total score (f = g + h)
Args:
graph: A dictionary representing the graph. Keys are nodes, values are
dictionaries where keys are neighbors and values are edge costs.
start: The starting node.
goal: The goal node.
heuristic_fn: A function that takes a state and returns its heuristic value
(estimated distance to goal).
Returns:
A list of nodes representing the path from start to goal if found,
otherwise None.
"""
frontier = []
explored = set()
start_node = Node(start)
start_node.h_score = heuristic_fn(start, goal)
start_node.f_score = start_node.g_score + start_node.h_score
heappush(frontier, start_node)
while frontier:
current = heappop(frontier)
explored.add(current.state)
if current.state == goal:
# Found the goal! Reconstruct the path
path = []
while current:
path.append(current.state)
current = current.parent
return path[::-1]
graph = {
'A': {'B': 1, 'C': 3},
'B': {'D': 2, 'E': 4},
'C': {'D': 5},
'D': {'E': 1},
'E': {}
}
start = 'A'
goal = 'E'
if path:
print("Path found:", path)
else:
print("No path found")
This implementation builds upon Uniform Cost Search (UCS) and Greedy
Best-First Search (GBFS). It uses a priority queue to prioritize nodes based on
their f-score (f = g + h), where g_score is the cost from the start to the
current node and h_score is a heuristic estimate of the cost from the current
node to the goal. The code maintains a set of explored nodes to avoid
revisiting them.
Note:
This example uses a simple distance heuristic. You'll need to replace it
with a more specific heuristic function depending on your problem
domain.
The code uses a copy of the graph to avoid modifying the original
graph during the search.