Introduction
In the past few lessons, we've defined what graphs are and explored the two primary ways to represent them in code: the adjacency matrix and the adjacency list. You've implemented both in C++, and you've seen that while a matrix is a simple 2D array, a list is more dynamic, storing only the connections that actually exist.
This lesson brings everything together by formally comparing these two representations. Our goal is to analyze their trade-offs in terms of time and space complexity. This analysis is not just a theoretical exercise; it is the key to deciding which tool to use for a given problem. For a competitive programmer, making the right choice here is often the difference between a solution that is accepted and one that exceeds time or memory limits.
Space Complexity: The Deciding Factor
The most significant difference between the two representations is the amount of memory they consume. Let's formalize what we've touched upon previously.
- Adjacency Matrix: Requires space for a matrix, regardless of how many edges are in the graph. The space complexity is therefore .
- Adjacency List: Stores a list for each vertex. The total size is the sum of the lengths of these lists. Since each edge
(u, v)adds one entry (tou's list in a directed graph) or two entries (tou's andv's lists in an undirected graph), the total number of entries stored is proportional to the number of edges, . We also need an array of size to hold the lists themselves. The space complexity is therefore .
In competitive programming, problems often involve sparse graphs, where the number of edges is much smaller than the maximum possible number of edges, which is on the order of . For sparse graphs, is often proportional to . Think of a graph representing a road network: a city might have millions of residents (vertices), but each intersection is only connected to a handful of other intersections (edges).
To see the dramatic impact of this difference, let's consider a practical example.
These lecture slides from the University of Texas at Arlington provide a concrete analysis of memory usage for a large, sparse graph.
Please read the section titled "Space Analysis". Focus on the calculation for a graph with 10 million vertices, where each vertex has at most 20 neighbors. The slides walk through the memory calculation for both the matrix and list representations.
As the slides demonstrate, for a graph with and an average degree of 20, the adjacency matrix would require around 12.5 Terabytes of memory. This is far beyond the capacity of any competitive programming server (or most computers in general!). In contrast, the adjacency list would require about 3.28 Gigabytes. While still large, this is orders of magnitude more feasible.
In a typical contest, might be up to . An adjacency matrix would require entries, which is about 40 GB if using 4-byte integers—impossible within a 256 MB or 512 MB memory limit. An adjacency list for a sparse graph with and would only need space for integers, which is easily manageable.
Time Complexity of Core Operations
Beyond space, the choice of representation affects the runtime of fundamental graph operations. There's no single "best" structure; it depends on what you need to do most often.
Let's analyze the common operations:
| Operation | Adjacency Matrix | Adjacency List (vector<vector<int>>) |
Analysis |
|---|---|---|---|
Check for an edge (u, v) |
The matrix excels here; it's a direct lookup. The list requires scanning u's neighbors. |
||
Get all neighbors of u |
The list is far more efficient. It only iterates over actual neighbors, while the matrix must scan an entire row of size . | ||
Add an edge (u, v) |
* | Both are fast. *For the list, this is the amortized time for push_back. If you must check for duplicates first, it becomes . |
|
Remove an edge (u, v) |
The matrix simply sets an entry to 0. The list requires finding the element v in u's list and then removing it, which is a linear-time operation for a vector. |
Most graph algorithms you will encounter, such as Breadth-First Search (BFS) and Depth-First Search (DFS), are built around the operation "for each neighbor of vertex u...". This is precisely where the adjacency list shines, making it the natural choice for implementing these algorithms efficiently. Using a matrix would slow these algorithms down from to , which is often too slow.
The following resources provide excellent summaries and direct comparisons.
This video from WilliamFiset's "Graph Theory" series provides a concise overview of the pros and cons of each representation.
Watch the segment on the adjacency matrix from the beginning, paying attention to the advantages (constant time lookup) and disadvantages (V^2 space). Then, watch the following segment on the adjacency list from here, which highlights its efficiency for sparse graphs.
For a final, clear summary in table format, the GeeksforGeeks article is an excellent reference.
Implementation of Graph in C++
This article includes a very useful table that directly contrasts the two graph representations.
Focus on the table under the heading "Difference". This table crystallizes the trade-offs we've just discussed, from memory usage to the efficiency of various operations.

The Competitive Programmer's Choice
So, which one should you use?
99% of the time in competitive programming, you will use an adjacency list.
The reasoning is simple and is a direct consequence of our analysis:
- Memory Constraints: Problems are designed with large enough that an solution is almost always too memory-intensive.
- Algorithmic Efficiency: The most common graph algorithms (traversals, shortest paths, etc.) depend on efficiently iterating through neighbors. The complexity for this operation in an adjacency list is fundamental to achieving standard algorithm runtimes like .
The rare cases where an adjacency matrix might be considered are:
- The graph is very small (e.g., ), so is not a problem.
- The graph is dense (i.e., is close to ), in which case the space of an adjacency list offers no real advantage over .
- The problem's logic requires many random edge existence checks, and does not heavily rely on neighbor iteration. This is characteristic of algorithms like Floyd-Warshall for all-pairs shortest paths, which we will cover much later in the course.
Conclusion
You have now completed the first module on graph fundamentals. You can define what a graph is, classify its properties, and, most importantly, represent it efficiently in C++.
Key Takeaways:
- Space Complexity is the primary driver for choosing a representation. Adjacency Matrix () is only for small or very dense graphs. Adjacency List () is ideal for the large, sparse graphs common in programming challenges.
- Time Complexity trade-offs are crucial. Matrices offer edge checking, while lists offer optimal neighbor iteration.
- For almost all competitive programming scenarios, the adjacency list is the superior and default choice due to memory constraints and its synergy with core graph algorithms.
With this solid foundation in place, you are ready to move on to the next module, where we will put these data structures to work. We will begin with the two most fundamental graph algorithms: Depth-First Search (DFS) and Breadth-First Search (BFS). These traversal techniques form the backbone of a vast number of more complex graph algorithms.