Python
Python

 

 

Graphs are fundamental data structures used to model pairwise relationships between objects. Efficient graph representation is crucial for optimizing various graph algorithms, such as traversal, pathfinding, and connectivity checks. The two most common ways to represent graphs are Adjacency Lists and Adjacency Matrices. This article provides a comprehensive guide to implementing both representations in Python, complete with detailed explanations, program structure, and documentation.

Understanding Graph Representations

What is a Graph?

A graph is a collection of vertices (also called nodes) and edges that connect pairs of vertices. Graphs can be directed or undirected, and they can contain cycles or be acyclic. Graphs are used to model various real-world systems, such as social networks, transportation networks, and communication systems.

Adjacency List

An Adjacency List represents a graph as a dictionary where each key is a vertex, and the corresponding value is a list of adjacent vertices. This representation is space-efficient for sparse graphs and allows for quick enumeration of a vertex’s neighbors.

  • Advantages:
    • Space-efficient for sparse graphs.
    • Efficiently enumerates all adjacent vertices.
  • Disadvantages:
    • Checking the existence of a specific edge can be slower compared to adjacency matrices.

Adjacency Matrix

An Adjacency Matrix represents a graph as a 2D list (matrix) where each cell [i][j] indicates the presence (and possibly the weight) of an edge from vertex i to vertex j. This representation is ideal for dense graphs and allows for constant-time edge checks.

  • Advantages:
    • Constant-time complexity for edge existence checks.
    • Simple and straightforward implementation.
  • Disadvantages:
    • Consumes more memory, especially for sparse graphs.
    • Inefficient for enumerating all adjacent vertices.

Program Structure and Explanation

The Python program is organized into two main classes:

  • GraphAdjacencyList: Implements the adjacency list representation.
  • GraphAdjacencyMatrix: Implements the adjacency matrix representation.

Each class provides methods to add edges, display the graph, and perform other utility functions. The main function demonstrates how to use these classes with example graphs.

Python Program: Graph Representation Using Adjacency List and Adjacency Matrix


# Graph Representation in Python using Adjacency List and Adjacency Matrix

from collections import defaultdict

class GraphAdjacencyList:
    def __init__(self, vertices):
        """
        Initializes a graph using an adjacency list.
        
        Parameters:
        vertices (list): A list of vertex identifiers.
        """
        self.graph = defaultdict(list)
        self.vertices = vertices

    def add_edge(self, u, v, directed=False):
        """
        Adds an edge to the graph.
        
        Parameters:
        u (any): The starting vertex.
        v (any): The ending vertex.
        directed (bool): If True, adds a directed edge from u to v.
                         If False, adds an undirected edge between u and v.
        """
        self.graph[u].append(v)
        if not directed:
            self.graph[v].append(u)

    def display(self):
        """
        Displays the graph as an adjacency list.
        """
        print("Adjacency List Representation:")
        for vertex in self.vertices:
            print(f"{vertex}: {self.graph[vertex]}")

class GraphAdjacencyMatrix:
    def __init__(self, vertices):
        """
        Initializes a graph using an adjacency matrix.
        
        Parameters:
        vertices (list): A list of vertex identifiers.
        """
        self.vertices = vertices
        self.V = len(vertices)
        # Initialize matrix with zeros
        self.matrix = [[0 for _ in range(self.V)] for _ in range(self.V)]
        # Mapping from vertex to index
        self.vertex_to_index = {vertex: idx for idx, vertex in enumerate(vertices)}

    def add_edge(self, u, v, directed=False, weight=1):
        """
        Adds an edge to the graph.
        
        Parameters:
        u (any): The starting vertex.
        v (any): The ending vertex.
        directed (bool): If True, adds a directed edge from u to v.
                         If False, adds an undirected edge between u and v.
        weight (int or float): The weight of the edge.
        """
        i = self.vertex_to_index[u]
        j = self.vertex_to_index[v]
        self.matrix[i][j] = weight
        if not directed:
            self.matrix[j][i] = weight

    def display(self):
        """
        Displays the graph as an adjacency matrix.
        """
        print("Adjacency Matrix Representation:")
        # Print header
        print(" ", end="\t")
        for vertex in self.vertices:
            print(vertex, end="\t")
        print()
        # Print rows
        for i, vertex in enumerate(self.vertices):
            print(vertex, end="\t")
            for j in range(self.V):
                print(self.matrix[i][j], end="\t")
            print()

def main():
    """
    Main function to demonstrate graph representations.
    """
    # Example 1: Undirected Graph using Adjacency List
    print("Example 1: Undirected Graph using Adjacency List")
    vertices1 = ['A', 'B', 'C', 'D', 'E']
    graph_list1 = GraphAdjacencyList(vertices1)
    edges1 = [
        ('A', 'B'),
        ('A', 'C'),
        ('B', 'D'),
        ('C', 'D'),
        ('C', 'E')
    ]
    for u, v in edges1:
        graph_list1.add_edge(u, v)
    graph_list1.display()
    print("\n")

    # Example 2: Directed Graph using Adjacency List
    print("Example 2: Directed Graph using Adjacency List")
    vertices2 = ['1', '2', '3', '4']
    graph_list2 = GraphAdjacencyList(vertices2)
    edges2 = [
        ('1', '2'),
        ('1', '3'),
        ('3', '4')
    ]
    for u, v in edges2:
        graph_list2.add_edge(u, v, directed=True)
    graph_list2.display()
    print("\n")

    # Example 3: Undirected Graph using Adjacency Matrix
    print("Example 3: Undirected Graph using Adjacency Matrix")
    vertices3 = ['P', 'Q', 'R', 'S']
    graph_matrix3 = GraphAdjacencyMatrix(vertices3)
    edges3 = [
        ('P', 'Q', False, 2),
        ('P', 'R', False, 4),
        ('Q', 'R', False, 1),
        ('Q', 'S', False, 7),
        ('R', 'S', False, 3)
    ]
    for u, v, directed, weight in edges3:
        graph_matrix3.add_edge(u, v, directed, weight)
    graph_matrix3.display()
    print("\n")

    # Example 4: Directed Graph using Adjacency Matrix
    print("Example 4: Directed Graph using Adjacency Matrix")
    vertices4 = ['X', 'Y', 'Z']
    graph_matrix4 = GraphAdjacencyMatrix(vertices4)
    edges4 = [
        ('X', 'Y', True, 5),
        ('Y', 'Z', True, 3),
        ('X', 'Z', True, 10)
    ]
    for u, v, directed, weight in edges4:
        graph_matrix4.add_edge(u, v, directed, weight)
    graph_matrix4.display()

if __name__ == "__main__":
    main()
    

Program Documentation

Class: GraphAdjacencyList

Represents a graph using an adjacency list.

  • Method: __init__(self, vertices)
    • Initializes the graph with the provided list of vertices.
  • Method: add_edge(self, u, v, directed=False)
    • Adds an edge between vertex u and vertex v.
    • If directed is True, adds a directed edge from u to v.
    • If directed is False, adds an undirected edge between u and v.
  • Method: display(self)
    • Displays the graph as an adjacency list.

Class: GraphAdjacencyMatrix

Represents a graph using an adjacency matrix.

  • Method: __init__(self, vertices)
    • Initializes the graph with the provided list of vertices and creates an adjacency matrix initialized to zero.
    • Maps each vertex to a unique index for matrix representation.
  • Method: add_edge(self, u, v, directed=False, weight=1)
    • Adds an edge between vertex u and vertex v with the specified weight.
    • If directed is True, adds a directed edge from u to v.
    • If directed is False, adds an undirected edge between u and v.
  • Method: display(self)
    • Displays the graph as an adjacency matrix.

Function: main()

Serves as the entry point of the program. It creates example graphs, adds edges, and displays their representations using both adjacency lists and adjacency matrices.

How to Run the Program

    1. Ensure you have Python installed on your system (Python 3.x recommended).
    2. Copy the provided Python code into a file named graph_representation.py.
    3. Open a terminal or command prompt and navigate to the directory containing the graph_representation.py file.
    4. Run the program using the command:
python graph_representation.py
  1. The program will execute the defined examples and display the graph representations accordingly.

Example Output


Example 1: Undirected Graph using Adjacency List
Adjacency List Representation:
A: ['B', 'C']
B: ['A', 'D', 'E']
C: ['A', 'D', 'E']
D: ['B', 'C']
E: ['C', 'F']
F: []

Example 2: Directed Graph using Adjacency List
Adjacency List Representation:
1: ['2', '3']
2: ['3', '4']
3: ['4']
4: []

Example 3: Undirected Graph using Adjacency Matrix
Adjacency Matrix Representation:
	P	Q	R	S	
P	0	2	4	0	
Q	2	0	1	7	
R	4	1	0	3	
S	0	7	3	0	

Example 4: Directed Graph using Adjacency Matrix
Adjacency Matrix Representation:
	X	Y	Z	
X	0	5	10	
Y	0	0	3	
Z	0	0	0	
    

Customization

To use the program with different graphs, modify the vertices and edges lists within the main function. Ensure that:

  • All vertices are listed in the vertices list.
  • Edges are added using the add_edge method with appropriate parameters.

For example, to represent a new undirected graph with vertices [‘M’, ‘N’, ‘O’] and edges:


# New Example: Undirected Graph
print("Example 5: Undirected Graph using Adjacency List")
vertices5 = ['M', 'N', 'O']
graph_list5 = GraphAdjacencyList(vertices5)
edges5 = [
    ('M', 'N'),
    ('N', 'O'),
    ('O', 'M')
]
for u, v in edges5:
    graph_list5.add_edge(u, v)
graph_list5.display()
print()

print("Example 6: Undirected Graph using Adjacency Matrix")
vertices6 = ['M', 'N', 'O']
graph_matrix6 = GraphAdjacencyMatrix(vertices6)
edges6 = [
    ('M', 'N', False, 3),
    ('N', 'O', False, 4),
    ('O', 'M', False, 5)
]
for u, v, directed, weight in edges6:
    graph_matrix6.add_edge(u, v, directed, weight)
graph_matrix6.display()
    

Adding this new example to the main function will allow you to compute and display the graph representations for the specified graph.

Conclusion

Efficient graph representation is pivotal for optimizing graph algorithms and ensuring scalability in applications that rely on graph structures. The adjacency list and adjacency matrix are two fundamental methods for representing graphs, each with its own set of advantages and ideal use cases. This Python implementation provides clear and concise classes to handle both representations, enabling developers to choose the most suitable approach based on the specific requirements of their projects. By mastering these representations, you lay a strong foundation for tackling more complex graph-related challenges and algorithms.


Learn how to implement graph representations using adjacency lists and adjacency matrices in Python. This guide includes detailed program structure, comprehensive documentation, and example usage.

 

By Aditya Bhuyan

I work as a cloud specialist. In addition to being an architect and SRE specialist, I work as a cloud engineer and developer. I have assisted my clients in converting their antiquated programmes into contemporary microservices that operate on various cloud computing platforms such as AWS, GCP, Azure, or VMware Tanzu, as well as orchestration systems such as Docker Swarm or Kubernetes. For over twenty years, I have been employed in the IT sector as a Java developer, J2EE architect, scrum master, and instructor. I write about Cloud Native and Cloud often. Bangalore, India is where my family and I call home. I maintain my physical and mental fitness by doing a lot of yoga and meditation.

Leave a Reply

Your email address will not be published. Required fields are marked *

error

Enjoy this blog? Please spread the word :)