Graph Implementation

Graph Implementation: Adjacency Matrix and Adjacency List

Graphs are one of the most versatile and essential data structures in computer science. From mapping out social networks to determining the shortest path in GPS navigation, graphs model real-world connections flawlessly.

However, understanding what a graph is conceptually is only half the battle. To leverage graphs in your algorithms, you must know how to represent them in code. In this comprehensive guide, we will explore the two most popular ways to implement a graph in Data Structures and Algorithms (DSA): the Adjacency Matrix and the Adjacency List.


1. The Adjacency Matrix

An Adjacency Matrix is a 2D array (or matrix) of size V x V, where V represents the number of vertices (nodes) in the graph.

If a graph has 5 vertices, the adjacency matrix will be a 5x5 grid.

Advantages of Adjacency Matrix

Disadvantages of Adjacency Matrix

Example: Adjacency Matrix in Python

Adjacency Matrix Implementation

class Graph:
    def __init__(self, num_vertices):
        self.V = num_vertices
        # Initialize a V x V matrix with all zeros
        self.matrix = [[0 for _ in range(num_vertices)] for _ in range(num_vertices)]
    def add_edge(self, u, v, directed=False):
        # Add an edge from node u to node v
        self.matrix[u][v] = 1
        if not directed:
            # For undirected graphs, add an edge from v back to u
            self.matrix[v][u] = 1 
    def print_graph(self):
        print("Adjacency Matrix:")
        for row in self.matrix:
            print(row)
# Create a graph with 4 vertices (0 to 3)
g = Graph(4)
g.add_edge(0, 1)
g.add_edge(0, 2)
g.add_edge(1, 2)
g.add_edge(2, 3)

g.print_graph()


2. The Adjacency List

An Adjacency List represents a graph as an array (or dictionary) of lists. The size of the array is equal to the number of vertices.

Instead of storing every possible connection like a matrix, each vertex simply maintains a dynamic list containing only the vertices it is directly connected to.

Advantages of Adjacency List

Disadvantages of Adjacency List

Pro Tip: Most real-world networks (like road networks or the World Wide Web) are massive but highly sparse. Because of this, the Adjacency List is the most commonly used graph implementation in modern software engineering.

Example: Adjacency List in Python

Adjacency List Implementation

class GraphList:
    def __init__(self, num_vertices):
        self.V = num_vertices
        # Initialize a list of empty lists for each vertex
        self.adj_list = [[] for _ in range(num_vertices)]
    def add_edge(self, u, v, directed=False):
        # Add vertex v to the list of vertex u
        self.adj_list[u].append(v)
        if not directed:
            # For undirected graphs, add u to the list of v
            self.adj_list[v].append(u)
    def print_graph(self):
        print("Adjacency List:")
        for i in range(self.V):
            print(f"Vertex {i}: {self.adj_list[i]}")
# Create a graph with 4 vertices (0 to 3)
g_list = GraphList(4)
g_list.add_edge(0, 1)
g_list.add_edge(0, 2)
g_list.add_edge(1, 2)
g_list.add_edge(2, 3)

g_list.print_graph()


3. Directed and Weighted Graphs

So far, we've primarily discussed Undirected and Unweighted graphs. But real-world data often has strict directions (like Twitter followers) and costs (like distances between cities).

Visual representation of a Directed Weighted Graph alongside its Adjacency Matrix

A Directed Weighted Graph alongside its Adjacency Matrix representation.


4. Comparison Summary

To summarize, choosing between these two graph implementations depends heavily on the properties of your data and the operations you need to perform frequently.

Operation / Feature Adjacency Matrix Adjacency List
Space Complexity O(V^2) O(V + E)
Add Edge O(1) O(1)
Remove Edge O(1) O(V)
Check Edge Exists (Query) O(1) O(V)
Find all Neighbors O(V) O(Degree of V)
Best Used For... Dense Graphs Sparse Graphs

Note: Dense graphs are networks where the number of edges is close to the maximum possible number of edges (V^2). Sparse graphs have far fewer edges overall.


Exercise 1 of 2

?

Which graph implementation is generally the most memory-efficient choice for a highly sparse graph?

Exercise 2 of 2

?

What is the worst-case time complexity to check if an edge exists between two nodes in an Adjacency Matrix?