Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 70

Representing Graphs: Adjacency Matrix and List

Recommended resources

Hello! Welcome to the first lesson in our new module on Graphs.

In our last lesson, you saw how a maze problem could be viewed as a graph, where each cell is a node and a valid move is an edge. We used backtracking, a form of Depth-First Search, to find paths through it. Today, we'll formalize this concept by learning how to represent these complex, interconnected data structures in code.

This lesson directly addresses the learning outcome: Represent graphs using an adjacency matrix and an adjacency list. We'll explore these two fundamental methods, analyze their trade-offs, and see how to implement them in Java, setting the stage for all the graph algorithms to come.

1. What is a Graph?

At its core, a graph is a data structure used to model relationships. It consists of two main components:

  • Vertices (or Nodes): The fundamental entities within the graph. Think of these as cities on a map, people in a social network, or devices in a computer network.
  • Edges: The connections between pairs of vertices. These represent roads, friendships, or network cables.

Before we dive into representing them, it's crucial to understand a few key properties that define a graph's behavior. The happycoding.io article provides an excellent introduction to these concepts.

Graphs - happycoding.io

To start, please read this brief introduction to graphs, which covers the essential terminology we'll be using.

Read the sections titled 'Graphs', 'Connection Costs', and 'Direction'. Focus on understanding the difference between: Undirected vs. Directed graphs: Are connections two-way or one-way? Unweighted vs. Weighted graphs: Do edges simply exist, or do they have an associated cost/weight?

Understanding these distinctions is vital because your choice of representation will depend on them. For example, representing a directed, weighted graph is different from representing a simple, unweighted, undirected graph.


2. Representation 1: Adjacency Matrix

The first and most straightforward way to represent a graph is using an adjacency matrix. This is a 2D array, typically of size V x V (where V is the number of vertices).

The rules are simple:

  • matrix[i][j] = 1 if there is an edge from vertex i to vertex j.
  • matrix[i][j] = 0 if there is no edge.

For a weighted graph, instead of storing 1, you store the weight of the edge. A 0 or infinity can represent the absence of an edge.

For an undirected graph, the matrix will be symmetric across the main diagonal (since an edge from i to j implies an edge from j to i).

Graph Representations: Adjacency Matrix vs. Adjacency List
This image compares Adjacency Matrix and Adjacency List representations for both an undirected graph (top) and a directed graph (bottom). Notice the symmetry in the undirected matrix and the asymmetry in the directed one.

This video provides a great walkthrough of the concept and its implementation.

G-3. Graph Representation in Java | Two Ways to Represent

This video, 'Graph Representation in Java', from the 'take U forward' channel, clearly explains the adjacency matrix, its space complexity, and how to populate it based on typical problem inputs.

Watch from 02:08 to 05:23. Pay attention to how an edge (u, v) in an undirected graph leads to two entries in the matrix (adj[u][v] = 1 and adj[v][u] = 1).

Pros and Cons of Adjacency Matrix

  • Pros:
    • Fast Edge Lookup: Checking if an edge exists between two vertices i and j is an O(1) operation (just look up matrix[i][j]).
  • Cons:
    • High Space Complexity: It requires O(V^2) space, regardless of the number of edges. This is very inefficient for sparse graphs (graphs with many vertices but few edges), which are common in practice.
    • Slow Neighbor Iteration: To find all neighbors of a vertex, you must iterate through its entire row, which takes O(V) time.

3. Representation 2: Adjacency List

The adjacency list is the most common way to represent graphs in competitive programming and technical interviews, primarily because it's more space-efficient for sparse graphs.

The concept is an array of lists. The list at index i stores all the vertices that are adjacent to vertex i.

In Java, this is typically implemented as:

  • ArrayList<ArrayList<Integer>> adj;
  • Or, more flexibly, Map<Integer, List<Integer>> adj;
Adjacency List Representation of an Undirected Graph
An undirected graph on the left and its corresponding adjacency list on the right. Each node in the list points to a collection of its neighbors.

Let's return to the video from 'take U forward' to see how this works.

G-3. Graph Representation in Java | Two Ways to Represent

The same video also provides an excellent explanation of the adjacency list representation, directly comparing it to the matrix.

Now watch from 05:23 to 09:31. Notice how the space complexity is related to the number of edges (M), making it much more efficient. Also, observe how to handle directed and weighted graphs.

For weighted graphs, you can't just store a list of integers. Instead, you'd store a list of objects or pairs, where each element contains the neighbor and the weight of the edge to it. A common Java implementation would be ArrayList<ArrayList<Pair<Integer, Integer>>>.

Pros and Cons of Adjacency List

  • Pros:
    • Space Efficient: Requires O(V + E) space (where E is the number of edges). This is significantly better than O(V^2) for sparse graphs.
    • Fast Neighbor Iteration: Iterating through all neighbors of a vertex u is efficient, as you only traverse the list of its actual neighbors.
  • Cons:
    • Slower Edge Lookup: To check if an edge exists between u and v, you have to search through u's adjacency list, which can take up to O(degree(u)) time in the worst case (where degree(u) is the number of neighbors).
Test your understanding!

You are tasked with modeling the entire flight network of a major airline. There are thousands of airports (vertices), but any given airport only has direct flights (edges) to a few dozen other airports.

Which representation, adjacency matrix or adjacency list, would be more suitable and why?

Show answer

An adjacency list would be far more suitable. The graph is sparse: the number of vertices V (airports) is large, but the number of edges E (flights) is relatively small compared to the maximum possible number of edges (V^2).

  • Using an adjacency matrix would require V^2 space, which would be enormous and mostly filled with zeros (representing no direct flight).
  • An adjacency list would only use O(V + E) space, which is much more memory-efficient in this scenario.

4. Implementation in Java

In most programming challenges, you are given the graph as a list of edges. A common first step is to convert this input into a more usable format like an adjacency list.

A typical input format looks like this:

  1. The first line contains two integers: N (number of vertices) and M (number of edges).
  2. The next M lines each contain two integers, u and v, representing an edge between vertex u and vertex v.

The happycoding.io resource has an excellent section showing how to perform this conversion.

Graphs - Converting Input Data

This section of the 'happycoding.io' article is highly practical. It shows concrete Java code for converting a list of edges into both an adjacency matrix and an adjacency list.

Focus on the section 'Converting Input Data'. Study the code snippets for building an adjacency matrix and, more importantly, for building an adjacency list. This is a fundamental pattern you will use repeatedly.

Here's a concise example of how to build an adjacency list for an undirected graph in Java, based on the pattern you just reviewed:

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class GraphRepresentation {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        System.out.println("Enter number of vertices (N) and edges (M):");
        int n = sc.nextInt(); // Number of vertices
        int m = sc.nextInt(); // Number of edges

        // Adjacency List: An ArrayList of ArrayLists.
        // We use n+1 size for 1-based indexing, which is common in problems.
        ArrayList<ArrayList<Integer>> adj = new ArrayList<>(n + 1);

        // Initialize an empty list for each vertex.
        for (int i = 0; i <= n; i++) {
            adj.add(new ArrayList<>());
        }

        System.out.println("Enter " + m + " edges (u v):");
        for (int i = 0; i < m; i++) {
            int u = sc.nextInt();
            int v = sc.nextInt();

            // Since it's an undirected graph, add an edge from u to v
            adj.get(u).add(v);
            // AND an edge from v to u
            adj.get(v).add(u);
        }

        // Print the adjacency list to verify
        System.out.println("\nAdjacency List:");
        for (int i = 1; i <= n; i++) {
            System.out.print("Vertex " + i + ": ");
            for (Integer neighbor : adj.get(i)) {
                System.out.print(neighbor + " ");
            }
            System.out.println();
        }
        sc.close();
    }
}

This code snippet is a template you can adapt for many graph problems. For a directed graph, you would simply omit the adj.get(v).add(u); line.


Conclusion

You've now taken your first and most crucial step into the world of graphs. Understanding how to represent them is the foundation upon which all other graph algorithms are built.

Key Takeaways:

  • A graph is defined by its vertices and edges, and can be directed/undirected and weighted/unweighted.
  • An Adjacency Matrix uses O(V^2) space and offers O(1) edge lookups. It is best for dense graphs where E is close to V^2.
  • An Adjacency List uses O(V + E) space and is efficient for iterating over neighbors. It is the preferred choice for sparse graphs and most competitive programming problems.
  • You should be comfortable converting a standard edge-list input into an adjacency list representation in Java.

Preview of the Next Lesson:
Now that we know how to store a graph, how do we explore it? In the next lesson, we will learn our first formal graph traversal algorithm: Breadth-First Search (BFS). We'll use the adjacency list you learned to build today to implement BFS, which explores the graph level by level and is the key to finding the shortest path in unweighted graphs.