Data Structures & Algorithms CIS318
Spring 2025 Lab Report
Obtained Marks
Total Marks
Lab Engineer Signature &
Comments
Section & Group
Students’ Name
1. Shaheer Baig
Section: Electronics Registration No: BS-21-GB-104578
Experiment No: 12 Date of Submission: 18 May 2025
Experiment Title:
Implementation of Graph
Batch: BSEE Teacher:
2021-25 Dr. Kamran Safdar
Semester 8th Lab Engineer:
M Hunzilah Ahmad
Adam Abbas
Department of Electrical Engineering
Lab 12: Implementation of Graph
12.1 Abstract
This lab focuses on the implementation of graph data structures in Python, par-
ticularly using adjacency matrix and adjacency list representations. It aims to
deepen understanding of graph-based modeling through the development of ba-
sic operations such as adding and removing edges, and simulating directed and
undirected graphs. Additionally, the experiment provides insight into how these
data structures support efficient graph traversal and management, which are es-
sential in solving real-world computational problems.
12.2 Objectives
The primary objectives of this lab are:
• To implement a graph data structure using both adjacency matrix and
adjacency list.
• To perform edge addition and removal operations on both directed and
undirected graphs.
• To understand the difference between graph representations and their
space-time trade-offs.
• To enhance skills in data structure modeling for real-time applications.
12.3 Background
A graph is a fundamental non-linear data structure used to model relationships
between entities. It consists of a set of vertices (nodes) and edges (links) connect-
ing pairs of vertices. Graphs can be directed or undirected, and are widely used
in networking, routing, and social media analysis. In this lab, two core rep-
resentations—adjacency matrix and adjacency list—are explored to understand
their advantages and use cases.
Task 1
1
Lab 12
1 class UndirectedGraph:
2 def __init__(self, vertices):
3 self.V = vertices
4 self.adj_matrix = [[0] * vertices for _ in range(
vertices)]
5 self.adj_list = {i: [] for i in range(vertices)}
6
7 def add_edge(self, u, v):
8 # Adjacency Matrix
9 self.adj_matrix[u][v] = 1
10 self.adj_matrix[v][u] = 1
11 # Adjacency List
12 self.adj_list[u].append(v)
13 self.adj_list[v].append(u)
14
15 def remove_edge(self, u, v):
16 # Adjacency Matrix
17 self.adj_matrix[u][v] = 0
18 self.adj_matrix[v][u] = 0
19 # Adjacency List
20 if v in self.adj_list[u]: self.adj_list[u].remove(v)
21 if u in self.adj_list[v]: self.adj_list[v].remove(u)
22
23 def display(self):
24 print("Adjacency Matrix:")
25 for row in self.adj_matrix:
26 print(row)
27 print("\nAdjacency List:")
28 for key in self.adj_list:
29 print(f"{key}: {self.adj_list[key]}")
30
31
32 # Initialize graph
33 ug = UndirectedGraph(5)
34 edges = [(0, 1), (0, 4), (1, 2), (1, 3), (1, 4), (2, 3), (3,
4)]
35
36 for u, v in edges:
37 ug.add_edge(u, v)
38
39 ug.display()
40
41 # Example: remove edge (1, 4)
42 ug.remove_edge(1, 4)
43 print("\nAfter removing edge (1, 4):")
44 ug.display()
2
Lab 12
Result
The graph with 5 vertices and 7 edges was successfully represented using both adja-
cency matrix and adjacency list. After removing the edge between vertex 1 and
vertex 4, both representations were correctly updated (see Figure 12.1).
Figure 12.1: Representation of an Undirected Graph before and after Edge Removal
Task 2
1 class DirectedGraph:
2 def __init__(self, vertices):
3 self.V = vertices
4 self.adj_matrix = [[0] * vertices for _ in range(
vertices)]
5 self.adj_list = {i: [] for i in range(vertices)}
6
7 def add_edge(self, u, v):
8 # Adjacency Matrix
9 self.adj_matrix[u][v] = 1
10 # Adjacency List
11 self.adj_list[u].append(v)
12
13 def display(self):
14 print("Adjacency Matrix:")
3
Lab 12
15 for row in self.adj_matrix:
16 print(row)
17 print("\nAdjacency List:")
18 for key in self.adj_list:
19 print(f"{key}: {self.adj_list[key]}")
20
21 # Initialize graph
22 dg = DirectedGraph(4)
23 edges = [(0, 1), (0, 2), (1, 2), (2, 0), (2, 3)]
24
25 for u, v in edges:
26 dg.add_edge(u, v)
27
28 dg.display()
Result
A directed graph with 4 vertices and 5 edges was created and represented using
adjacency matrix and adjacency list. The output accurately reflects one-way
connections from source to destination vertices (see Figure 12.2).
Figure 12.2: Representation of a Directed Graph Using Adjacency Matrix and Adja-
cency List
Task 3: Time and Space Complexity Comparison
• Adjacency Matrix:
– Time Complexity:
∗ Add/Remove Edge: O(1)
∗ Check Edge Existence: O(1)
∗ Traverse All Neighbors: O(V )
– Space Complexity: O(V 2 )
4
Lab 12
• Adjacency List:
– Time Complexity:
∗ Add Edge: O(1) (amortized)
∗ Remove Edge / Check Existence: O(k), where k is the number of
neighbors
∗ Traverse All Neighbors: O(k)
– Space Complexity: O(V + E)
The adjacency matrix is suitable for dense graphs where quick edge access is
required, while the adjacency list is more efficient for sparse graphs due to its lower
space usage.
12.4 Discussion
• The lab reinforced the understanding of graph representations using both ad-
jacency matrices and adjacency lists.
• It was observed that adjacency matrices provide constant-time edge lookup
but consume more memory for sparse graphs.
• Adjacency lists proved more memory-efficient and intuitive for visualizing
actual connections between vertices.
• Implementing edge addition and removal helped in understanding how dy-
namic graphs are manipulated in real time.
• Directed and undirected graphs demonstrated the importance of edge di-
rectionality in algorithm behavior and structure.
12.5 Conclusion
In this lab, we successfully implemented graph data structures using both adja-
cency matrix and adjacency list representations. By performing edge operations
and analyzing time-space trade-offs, we gained insights into selecting the appropri-
ate structure for different applications. The experiment enhanced our understanding
of graph traversal, storage optimization, and the functional distinction between
directed and undirected graphs. Overall, this lab served as a practical foundation
for applying graph theory in real-world computing problems.