0% found this document useful (0 votes)
11 views6 pages

AI Lab5

lab 5

Uploaded by

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

AI Lab5

lab 5

Uploaded by

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

Lab 5

Graphs:
Consider a simple (directed) graph (digraph) having six nodes (A-F) and the following arcs
(directed edges):
A -> B
A -> C
B -> C
B -> D
C -> D
C -> F
D -> C
D -> E
E -> F
F -> C
F -> E

It can be represented by the following Python data structure:

graph = {'A': ['B', 'C'],


'B': ['C', 'D'],
'C': ['D',’F’],
'D': ['C',’E’],
'E': ['F'],
'F': ['C',’E’]}

This is a dictionary whose keys are the nodes of the graph. For each key, the corresponding
value is a list containing the nodes that are connected by a direct arc from this node. This is
about as simple as it gets (even simpler, the nodes could be represented by numbers instead
of names, but names are more convenient and can easily be made to carry more information,
such as city names).

Let's write a simple function to determine a path between two nodes. It takes a graph and the
start and end nodes as arguments. It will return a list of nodes (including the start and end
nodes) comprising the path. When no path can be found, it returns None. The same node will
not occur more than once on the path returned (i.e. it won't contain cycles). The algorithm
uses an important technique called backtracking: it tries each possibility in turn until it finds a
solution.

def find_path(graph, start, end, path=[]):


path = path + [start]
if start == end:
return path
if start not in graph:
return None
for node in graph[start]:
if node not in path:
newpath = find_path(graph, node, end, path)
if newpath: return newpath
return None
A sample run of the function find_path() (using the graph above):
>>> find_path(graph, 'A', 'D')
['A', 'B', 'C', 'D']

Example 2:
class Graph:
def __init__(self, nodes=None, edges=None):
"""Initialize a graph object.
Args:
nodes: Iterator of nodes. Each node is an object.
edges: Iterator of edges. Each edge is a tuple of 2 nodes.
"""
self.nodes, self.adj = [], {}
if nodes != None:
self.add_nodes_from(nodes)
if edges != None:
self.add_edges_from(edges)

def length(self):
"""Returns the number of nodes in the graph.
>>> g = Graph(nodes=[x for x in range(7)])
>>> len(g)
7
"""
return len(self.nodes)

def traverse(self):
return 'V: %s\nE: %s' % (self.nodes, self.adj)

def add_node(self, n):


if n not in self.nodes:
self.nodes.append(n)
self.adj[n] = []

def add_edge(self, u, v): # undirected unweighted graph


self.adj[u] = self.adj.get(u, []) + [v]
self.adj[v] = self.adj.get(v, []) + [u]

def number_of_nodes(self):
return len(self.nodes)

def number_of_edges(self):
return sum(len(l) for _, l in self.adj.items()) // 2

class DGraph(Graph):
def add_edge(self, u, v):
self.adj[u] = self.adj.get(u, []) + [v]

class WGraph(Graph):
def __init__(self, nodes=None, edges=None):
"""Initialize a graph object.
Args:
nodes: Iterator of nodes. Each node is an object.
edges: Iterator of edges. Each edge is a tuple of 2 nodes and
a weight.
"""
self.nodes, self.adj, self.weight = [], {}, {}
if nodes != None:
self.add_nodes_from(nodes)
if edges != None:
self.add_edges_from(edges)

def add_edge(self, u, v, w):


self.adj[u] = self.adj.get(u, []) + [v]
self.adj[v] = self.adj.get(v, []) + [u]
self.weight[(u,v)] = w
self.weight[(v,u)] = w

def get_weight(self, u, v):


return self.weight[(u,v)]

class DWGraph(WGraph):
def add_edge(self, u, v, w):
self.adj[u] = self.adj.get(u, []) + [v]
self.weight[(u,v)] = w

Lab Task:
1. Change the function find path to return shortest path.
def find_shortest_path(graph, start, end, path=None):
if path is None:
path = [start]

if start == end:
return path

shortest_path = None # To store the shortest path

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


if neighbor not in path: # Prevent cycles
new_path = find_shortest_path(graph, neighbor, end, path +
[neighbor])

if new_path: # If a valid path is found


# Check if it's the shortest path found so far
if shortest_path is None or len(new_path) <
len(shortest_path):
shortest_path = new_path

return shortest_path

# Example graph
graph = {
"A": ["B", "C"],
"B": ["C", "D"],
"C": ["D", "F"],
"D": ["C", "E"],
"E": ["F"],
"F": ["C", "E"],
}

# Test the function


print(find_shortest_path(graph, "A", "F"))

2.
C
onsider a simple (directed) graph (digraph) having six nodes (A-F) and the following arcs
(directed edges) with respective cost of edge given in parentheses:
A -> B (2)
A -> C (1)
B -> C (2)
B -> D (5)
C -> D (1)
C -> F (3)
D -> C (1)
D -> E (4)
E -> F (3)
F -> C (1)
F -> E (2)
Using the code for a directed weighted graph in Example 2, instantiate an object of DWGraph
in __main__, add the nodes and edges of the graph using the relevant functions, and
implement a function find_path() that takes starting and ending nodes as arguments and
returns at least one path (if one exists) between those two nodes. The function should also
keep track of the cost of the path and return the total cost as well as the path. Print the path
and its cost in __main__.
# Directed Weighted Graph class
class DWGraph:
def __init__(self):
self.nodes = [] # List of nodes
self.adj = {} # Adjacency list for storing neighbors
self.weight = {} # Weight for storing edges' weights

def add_node(self, n):


"""Add a node to the graph."""
if n not in self.nodes:
self.nodes.append(n)
self.adj[n] = []

def add_edge(self, u, v, w):


"""Add a directed, weighted edge to the graph."""
self.adj[u].append(v)
self.weight[(u, v)] = w

def get_weight(self, u, v):


"""Get the weight of the edge from u to v."""
return self.weight.get((u, v), float("inf"))

# Function to find a path between two nodes


def find_path(graph, start, end):
"""Returns any valid path and its cost from 'start' to 'end'."""
stack = [(start, [start], 0)] # (current_node, path, cost)
visited = set()

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

if node == end:
return (cost, path)

if node not in visited:


visited.add(node)
for neighbor in graph.adj.get(node, []):
stack.append(
(
neighbor,
path + [neighbor],
cost + graph.get_weight(node, neighbor),
)
)

return None # No path found

# Test example
if __name__ == "__main__":
graph = DWGraph()

# Adding nodes
for node in ["A", "B", "C", "D", "E", "F"]:
graph.add_node(node)

# Adding edges (with weights)


edges = [
("A", "B", 2),
("A", "C", 1),
("B", "C", 2),
("B", "D", 5),
("C", "D", 1),
("C", "F", 3),
("D", "C", 1),
("D", "E", 4),
("E", "F", 3),
("F", "C", 1),
("F", "E", 2),
]

for u, v, w in edges:
graph.add_edge(u, v, w)

# Finding a path from A to E


start_node = "A"
end_node = "E"
result = find_path(graph, start_node, end_node)

if result:
cost, path = result
print(f"Path from {start_node} to {end_node}: {path} with total
cost {cost}")
else:
print(f"No path found from {start_node} to {end_node}")

You might also like