Graph Representation
Why Representation Matters
The graph representation you choose determines the time and space complexity of every operation — BFS, DFS, shortest path, neighbour lookup, edge check. There is no universally best choice: each representation has a different trade-off.
REFERENCE GRAPH for all examples:
Undirected weighted graph:
0 ──4── 1 ──8── 2
| | |
11 7 2──5
| | |
7 ──9── 6 3──4
/ \
2 6
/ \
8 4
Simplified for clarity — use this graph:
0 ───4─── 1
│ │ \
│8 7 \6
│ │ \
7 ──9─── 6 ─2── 5
\
3
Let's use a cleaner 5-vertex example:
Vertices: {0, 1, 2, 3, 4}
Edges (undirected, weighted):
(0,1,4) (0,2,1) (1,2,2)
(1,3,5) (2,4,8) (3,4,3)
0 ──4── 1
│ │ \
1 2 5
│ │ \
2 ──8── 4 ──3─ 3
This 5-vertex, 6-edge graph is used for ALL representations below.
Representation 1: Adjacency Matrix
Store a V × V 2D array. matrix[u][v] = weight (or 1 for unweighted) if edge (u,v) exists, 0 otherwise.
Graph: 0─4─1, 0─1─2, 1─2─2, 1─5─3, 2─8─4, 3─4─3
Weighted adjacency matrix (undirected → symmetric):
0 1 2 3 4
0 [ 0, 4, 1, 0, 0 ]
1 [ 4, 0, 2, 5, 0 ]
2 [ 1, 2, 0, 0, 8 ]
3 [ 0, 5, 0, 0, 3 ]
4 [ 0, 0, 8, 3, 0 ]
matrix[0][1] = 4 ← edge (0,1) has weight 4
matrix[1][0] = 4 ← symmetric (undirected)
matrix[0][3] = 0 ← no edge between 0 and 3
UNWEIGHTED: replace weights with 1 (edge exists) / 0 (no edge)
DIRECTED: matrix is NOT symmetric (matrix[u][v] ≠ matrix[v][u] in general)
1public class AdjacencyMatrix {
2
3 private int[][] matrix;
4 private int vertices;
5 private boolean directed;
6
7 public AdjacencyMatrix(int vertices, boolean directed) {
8 this.vertices = vertices;
9 this.directed = directed;
10 this.matrix = new int[vertices][vertices];
11 }
12
13 // Add edge — O(1)
14 public void addEdge(int u, int v, int weight) {
15 matrix[u][v] = weight;
16 if (!directed) matrix[v][u] = weight; // Undirected: both directions
17 }
18
19 // Remove edge — O(1)
20 public void removeEdge(int u, int v) {
21 matrix[u][v] = 0;
22 if (!directed) matrix[v][u] = 0;
23 }
24
25 // Check if edge exists — O(1)
26 public boolean hasEdge(int u, int v) { return matrix[u][v] != 0; }
27
28 // Get edge weight — O(1)
29 public int getWeight(int u, int v) { return matrix[u][v]; }
30
31 // Get all neighbours of vertex u — O(V)
32 public java.util.List<Integer> neighbours(int u) {
33 java.util.List<Integer> result = new java.util.ArrayList<>();
34 for (int v = 0; v < vertices; v++) {
35 if (matrix[u][v] != 0) result.add(v);
36 }
37 return result;
38 }
39
40 // Print matrix
41 public void print() {
42 System.out.print(" ");
43 for (int i = 0; i < vertices; i++) System.out.printf("%3d", i);
44 System.out.println();
45 for (int u = 0; u < vertices; u++) {
46 System.out.printf("%3d ", u);
47 for (int v = 0; v < vertices; v++) System.out.printf("%3d", matrix[u][v]);
48 System.out.println();
49 }
50 }
51
52 public static void main(String[] args) {
53 AdjacencyMatrix g = new AdjacencyMatrix(5, false); // Undirected
54 g.addEdge(0, 1, 4); g.addEdge(0, 2, 1);
55 g.addEdge(1, 2, 2); g.addEdge(1, 3, 5);
56 g.addEdge(2, 4, 8); g.addEdge(3, 4, 3);
57
58 g.print();
59
60 System.out.println("Has edge (0,1): " + g.hasEdge(0, 1)); // true
61 System.out.println("Has edge (0,3): " + g.hasEdge(0, 3)); // false
62 System.out.println("Weight (1,3): " + g.getWeight(1, 3)); // 5
63 System.out.println("Neighbours(1): " + g.neighbours(1)); // [0,2,3]
64 }
65}Output (matrix):
0 1 2 3 4
0 0 4 1 0 0
1 4 0 2 5 0
2 1 2 0 0 8
3 0 5 0 0 3
4 0 0 8 3 0
Has edge (0,1): true
Has edge (0,3): false
Weight (1,3): 5
Neighbours(1): [0, 2, 3]
Representation 2: Adjacency List
Store an array of lists. adj[u] holds all vertices (and optionally weights) directly connected to u.
Graph: 0─4─1, 0─1─2, 1─2─2, 1─5─3, 2─8─4, 3─4─3 Adjacency list (undirected, weighted): 0: [(1,4), (2,1)] 1: [(0,4), (2,2), (3,5)] 2: [(0,1), (1,2), (4,8)] 3: [(1,5), (4,3)] 4: [(2,8), (3,3)] (neighbour, weight) pairs Each edge (u,v,w) creates TWO entries for undirected: u's list: (v, w) v's list: (u, w) Total entries: V + 2E = 5 + 12 = 17 entries (much less than 25 for matrix) UNWEIGHTED version: store just neighbour IDs 0: [1, 2] 1: [0, 2, 3] ...
1import java.util.*;
2
3public class AdjacencyList {
4
5 // Inner class for weighted edge
6 static class Edge {
7 int dest, weight;
8 Edge(int dest, int weight) { this.dest = dest; this.weight = weight; }
9 @Override public String toString() { return "(" + dest + "," + weight + ")"; }
10 }
11
12 private List<List<Edge>> adj;
13 private int vertices;
14 private boolean directed;
15
16 public AdjacencyList(int vertices, boolean directed) {
17 this.vertices = vertices;
18 this.directed = directed;
19 adj = new ArrayList<>();
20 for (int i = 0; i < vertices; i++) adj.add(new ArrayList<>());
21 }
22
23 // Add edge — O(1)
24 public void addEdge(int u, int v, int weight) {
25 adj.get(u).add(new Edge(v, weight));
26 if (!directed) adj.get(v).add(new Edge(u, weight));
27 }
28
29 // Remove edge — O(degree(u))
30 public void removeEdge(int u, int v) {
31 adj.get(u).removeIf(e -> e.dest == v);
32 if (!directed) adj.get(v).removeIf(e -> e.dest == u);
33 }
34
35 // Check if edge exists — O(degree(u))
36 public boolean hasEdge(int u, int v) {
37 return adj.get(u).stream().anyMatch(e -> e.dest == v);
38 }
39
40 // Get edge weight — O(degree(u))
41 public int getWeight(int u, int v) {
42 return adj.get(u).stream()
43 .filter(e -> e.dest == v)
44 .mapToInt(e -> e.weight)
45 .findFirst()
46 .orElse(0);
47 }
48
49 // Get all neighbours — O(1) to get list, O(degree) to iterate
50 public List<Edge> neighbours(int u) { return adj.get(u); }
51
52 // Get degree of vertex u
53 public int degree(int u) { return adj.get(u).size(); }
54
55 // Print adjacency list
56 public void print() {
57 for (int u = 0; u < vertices; u++) {
58 System.out.print(u + ": " + adj.get(u));
59 System.out.println();
60 }
61 }
62
63 public static void main(String[] args) {
64 AdjacencyList g = new AdjacencyList(5, false); // Undirected
65 g.addEdge(0, 1, 4); g.addEdge(0, 2, 1);
66 g.addEdge(1, 2, 2); g.addEdge(1, 3, 5);
67 g.addEdge(2, 4, 8); g.addEdge(3, 4, 3);
68
69 g.print();
70
71 System.out.println("Has edge (0,1): " + g.hasEdge(0, 1)); // true
72 System.out.println("Has edge (0,3): " + g.hasEdge(0, 3)); // false
73 System.out.println("Weight (1,3): " + g.getWeight(1, 3)); // 5
74 System.out.println("Degree(1): " + g.degree(1)); // 3
75 System.out.println("Neighbours(2): " + g.neighbours(2)); // [(0,1),(1,2),(4,8)]
76
77 // DIRECTED graph example
78 AdjacencyList dg = new AdjacencyList(4, true);
79 dg.addEdge(0, 1, 1); dg.addEdge(1, 2, 1); dg.addEdge(2, 3, 1); dg.addEdge(3, 0, 1);
80 System.out.println("\nDirected graph:");
81 dg.print();
82 }
83}Output (adjacency list): 0: [(1,4), (2,1)] 1: [(0,4), (2,2), (3,5)] 2: [(0,1), (1,2), (4,8)] 3: [(1,5), (4,3)] 4: [(2,8), (3,3)] Has edge (0,1): true Has edge (0,3): false Weight (1,3): 5 Degree(1): 3 Neighbours(2): [(0,1), (1,2), (4,8)]
Representation 3: Edge List
Store all edges as a flat list of (source, destination, weight) tuples. No vertex-indexed structure.
Graph: 0─4─1, 0─1─2, 1─2─2, 1─5─3, 2─8─4, 3─4─3 Edge list (undirected — each edge stored once): [(0,1,4), (0,2,1), (1,2,2), (1,3,5), (2,4,8), (3,4,3)] UNWEIGHTED version: [(0,1), (0,2), (1,2), (1,3), (2,4), (3,4)] DIRECTED version — only store directed edges, no duplicates. Space: O(E) — just the edge tuples. Perfect for algorithms that iterate over all edges: - Kruskal's MST (sort by weight, process each edge) - Bellman-Ford (relax all edges V-1 times) - Counting edges, finding min/max weight edge
1import java.util.*;
2
3public class EdgeList {
4
5 static class Edge implements Comparable<Edge> {
6 int u, v, weight;
7 Edge(int u, int v, int weight) { this.u = u; this.v = v; this.weight = weight; }
8
9 @Override
10 public int compareTo(Edge other) {
11 return Integer.compare(this.weight, other.weight); // Sort by weight
12 }
13
14 @Override
15 public String toString() { return "(" + u + "─" + v + "," + weight + ")"; }
16 }
17
18 private List<Edge> edges = new ArrayList<>();
19 private int vertices;
20 private boolean directed;
21
22 public EdgeList(int vertices, boolean directed) {
23 this.vertices = vertices;
24 this.directed = directed;
25 }
26
27 // Add edge — O(1)
28 public void addEdge(int u, int v, int weight) {
29 edges.add(new Edge(u, v, weight));
30 // For undirected, can add both directions OR just store once
31 // (convention varies by algorithm — Kruskal typically stores once)
32 }
33
34 // Check if edge exists — O(E)
35 public boolean hasEdge(int u, int v) {
36 return edges.stream().anyMatch(e ->
37 (e.u == u && e.v == v) ||
38 (!directed && e.u == v && e.v == u));
39 }
40
41 // Get edge weight — O(E)
42 public int getWeight(int u, int v) {
43 return edges.stream()
44 .filter(e -> (e.u == u && e.v == v) ||
45 (!directed && e.u == v && e.v == u))
46 .mapToInt(e -> e.weight)
47 .findFirst()
48 .orElse(0);
49 }
50
51 // Sort edges by weight — O(E log E) — used by Kruskal's
52 public void sortByWeight() { Collections.sort(edges); }
53
54 // Get all edges
55 public List<Edge> getEdges() { return edges; }
56
57 public void print() {
58 System.out.println("Edge list: " + edges);
59 System.out.println("Total edges: " + edges.size());
60 }
61
62 public static void main(String[] args) {
63 EdgeList g = new EdgeList(5, false);
64 g.addEdge(0, 1, 4); g.addEdge(0, 2, 1);
65 g.addEdge(1, 2, 2); g.addEdge(1, 3, 5);
66 g.addEdge(2, 4, 8); g.addEdge(3, 4, 3);
67
68 g.print();
69
70 System.out.println("Has (0,1): " + g.hasEdge(0, 1)); // true
71 System.out.println("Has (0,3): " + g.hasEdge(0, 3)); // false
72 System.out.println("Weight (2,4): " + g.getWeight(2, 4)); // 8
73
74 // Sort for Kruskal's MST
75 g.sortByWeight();
76 System.out.println("Sorted: " + g.getEdges());
77 // [(0─2,1), (1─2,2), (3─4,3), (0─1,4), (1─3,5), (2─4,8)]
78 }
79}Output:
Edge list: [(0,1,4),(0,2,1),(1,2,2),(1,3,5),(2,4,8),(3,4,3)]
Total edges: 6
Has (0,1): true
Has (0,3): false
Weight (2,4): 8
Sorted: [(0,2,1),(1,2,2),(3,4,3),(0,1,4),(1,3,5),(2,4,8)]
Converting Between Representations
MATRIX → LIST: O(V²) — must scan all V² cells MATRIX → EDGES: O(V²) — must scan all V² cells LIST → MATRIX: O(V + E) — add each edge into matrix LIST → EDGES: O(V + E) — collect edges from each list EDGES → LIST: O(V + E) — add each edge to appropriate list EDGES → MATRIX: O(V + E) — add each edge into matrix WHY MATRIX → LIST IS O(V²): Even if the graph has only E=V edges, you must check all V² matrix cells to find the non-zero entries. You cannot know which cells are non-zero without looking at each one.
1import java.util.*;
2
3public class ConvertRepresentations {
4
5 // Adjacency Matrix → Adjacency List — O(V²)
6 public static List<List<int[]>> matrixToList(int[][] matrix) {
7 int V = matrix.length;
8 List<List<int[]>> adj = new ArrayList<>();
9 for (int i = 0; i < V; i++) adj.add(new ArrayList<>());
10
11 for (int u = 0; u < V; u++)
12 for (int v = 0; v < V; v++)
13 if (matrix[u][v] != 0)
14 adj.get(u).add(new int[]{v, matrix[u][v]});
15
16 return adj;
17 }
18
19 // Adjacency List → Adjacency Matrix — O(V + E)
20 public static int[][] listToMatrix(List<List<int[]>> adj) {
21 int V = adj.size();
22 int[][] matrix = new int[V][V];
23 for (int u = 0; u < V; u++)
24 for (int[] edge : adj.get(u))
25 matrix[u][edge[0]] = edge[1];
26 return matrix;
27 }
28
29 // Adjacency List → Edge List — O(V + E)
30 public static List<int[]> listToEdges(List<List<int[]>> adj, boolean directed) {
31 List<int[]> edges = new ArrayList<>();
32 for (int u = 0; u < adj.size(); u++)
33 for (int[] edge : adj.get(u)) {
34 int v = edge[0], w = edge[1];
35 if (directed || u < v) // Avoid duplicates for undirected
36 edges.add(new int[]{u, v, w});
37 }
38 return edges;
39 }
40
41 // Edge List → Adjacency List — O(V + E)
42 public static List<List<int[]>> edgesToList(int V, List<int[]> edges, boolean directed) {
43 List<List<int[]>> adj = new ArrayList<>();
44 for (int i = 0; i < V; i++) adj.add(new ArrayList<>());
45 for (int[] e : edges) {
46 adj.get(e[0]).add(new int[]{e[1], e[2]});
47 if (!directed) adj.get(e[1]).add(new int[]{e[0], e[2]});
48 }
49 return adj;
50 }
51
52 public static void main(String[] args) {
53 int[][] matrix = {
54 {0, 4, 1, 0, 0},
55 {4, 0, 2, 5, 0},
56 {1, 2, 0, 0, 8},
57 {0, 5, 0, 0, 3},
58 {0, 0, 8, 3, 0}
59 };
60
61 // Matrix → List
62 List<List<int[]>> list = matrixToList(matrix);
63 System.out.println("Matrix → List (vertex 1's neighbours):");
64 for (int[] nb : list.get(1)) System.out.print("(" + nb[0] + "," + nb[1] + ") ");
65 System.out.println(); // (0,4) (2,2) (3,5)
66
67 // List → Edges
68 List<int[]> edges = listToEdges(list, false);
69 System.out.println("List → Edges (count): " + edges.size()); // 6
70 for (int[] e : edges) System.out.print("(" + e[0] + "-" + e[1] + "," + e[2] + ") ");
71 System.out.println();
72 }
73}Output:
Matrix → List:
0: [(1,4),(2,1)]
1: [(0,4),(2,2),(3,5)]
2: [(0,1),(1,2),(4,8)]
3: [(1,5),(4,3)]
4: [(2,8),(3,3)]
List → Edges (6 edges): [(0,1,4),(0,2,1),(1,2,2),(1,3,5),(2,4,8),(3,4,3)]
Edges → List → Matrix (row 1): [4, 0, 2, 5, 0]
Space and Time Comparison
GRAPH: V vertices, E edges Density: sparse = E << V², dense = E ≈ V² OPERATION Adj Matrix Adj List Edge List ────────────────────────────────────────────────────────── Space O(V²) O(V + E) O(E) Add edge O(1) O(1) O(1) Remove edge O(1) O(deg(u)) O(E) Has edge (u,v)? O(1) O(deg(u)) O(E) Get neighbours(v) O(V) O(deg(v)) O(E) Iterate ALL edges O(V²) O(V + E) O(E) Build structure O(V²) O(V + E) O(E) ALGORITHM SUITABILITY: BFS / DFS → Adjacency List (iterate neighbours) Dijkstra's → Adjacency List (iterate neighbours with weights) Bellman-Ford → Edge List (relax all edges V-1 times) Kruskal's MST → Edge List (sort edges by weight) Prim's MST → Adjacency List (iterate neighbours) Floyd-Warshall → Adjacency Matrix (all-pairs: matrix[i][j][k]) Has specific edge? → Adjacency Matrix (O(1) lookup) Dense graph ≥ V²/2 → Adjacency Matrix (space comparable to list) Sparse graph → Adjacency List (memory efficient) RECOMMENDATION: Default choice for most problems → ADJACENCY LIST Use matrix only when: O(1) edge check needed, or dense graph Use edge list only when: algorithm iterates all edges (Kruskal, Bellman-Ford)
When to Use Which Representation
USE ADJACENCY MATRIX when: ✓ Need O(1) edge existence check (e.g., "is u adjacent to v?") ✓ Graph is dense (E ≈ V²) — matrix space is acceptable ✓ Floyd-Warshall all-pairs shortest path (natural matrix DP) ✓ Transitive closure computation ✓ Small graphs where simplicity matters over memory USE ADJACENCY LIST when: ✓ Graph is sparse (most real-world graphs: roads, social, web) ✓ BFS / DFS traversal — iterate all neighbours efficiently ✓ Dijkstra's / Prim's — pick minimum weight neighbour ✓ Memory is a constraint ✓ DEFAULT choice for most graph problems USE EDGE LIST when: ✓ Kruskal's MST — sort edges by weight and process each ✓ Bellman-Ford — relax all edges V-1 times ✓ Need to iterate ALL edges without needing vertex-indexed access ✓ Building a graph from an input list of edges ✓ Storing a graph compactly (O(E) space)
Common Mistakes
Not storing both directions for undirected graphs in adjacency list. When edge (u,v) is undirected, add v to u's list AND add u to v's list. Forgetting the second insertion creates a directed graph implicitly — BFS/DFS from u will reach v but DFS from v won't reach u.
Using adjacency matrix for sparse graphs. For a social network with 1 million users and 10 million friendships: adjacency matrix = 10¹² entries (terabytes of RAM). Adjacency list = ~21 million entries (manageable). Always assess sparsity before choosing matrix.
Integer overflow in matrix indices for large graphs. For V = 100,000 vertices, V² = 10¹⁰ — exceeding 32-bit integer range for flat arrays. Either use long indices, or switch to adjacency list.
Checking edge (u,v) in adjacency list using linear scan when O(1) is needed. Adjacency list gives O(deg(u)) edge check. If O(1) is required, use either adjacency matrix or a hash set for each vertex's neighbour set (HashSet<Integer> instead of List<Integer>).
Edge list duplicates for undirected graphs. Undirected edge (u,v) should be stored ONCE in an edge list (by convention u < v). Storing both (u,v) and (v,u) doubles the edge count and causes problems in algorithms like Kruskal's which process each edge once.
Interview Questions
Q: An interviewer gives you a graph problem. Which representation do you choose?
Default to adjacency list. Ask: Is the graph sparse or dense? For sparse (most common — social networks, road networks, dependency graphs), adjacency list is memory-efficient and gives O(degree) neighbour access which BFS and DFS need. If the problem specifically requires O(1) edge check or describes a dense graph (complete or near-complete), use adjacency matrix. If the algorithm iterates all edges (Kruskal's, Bellman-Ford), build an edge list.
Q: Why is BFS/DFS time complexity written as O(V + E) and not O(V × degree)?
O(V + E) comes from adjacency list representation. BFS visits each vertex once (V operations for dequeue) and iterates each edge exactly once (E operations total across all neighbour lists). With adjacency matrix, BFS is O(V²) — each vertex requires scanning V entries to find its neighbours. So the O(V + E) guarantee specifically assumes adjacency list representation.
Q: How would you represent a multigraph (multiple edges between same vertices)?
Adjacency list handles multigraphs naturally — just add the edge multiple times to the lists, and the list can contain duplicate destinations. Adjacency matrix cannot represent multigraphs with a single integer per cell — you'd need to store edge count or use a matrix of lists. Edge list handles multigraphs trivially — just add multiple (u,v,w) tuples.
Summary
Three representations for a graph with V vertices and E edges:
Adjacency Matrix — O(V²) space, O(1) edge check:
- ›Build: allocate V×V array;
matrix[u][v] = weight - ›Undirected: symmetric (
matrix[u][v] = matrix[v][u]) - ›Best for: dense graphs, O(1) edge lookup, Floyd-Warshall
Adjacency List — O(V + E) space, O(deg) edge check:
- ›Build: array of lists;
adj[u].add((v, weight)) - ›Undirected: add to both lists
- ›Best for: sparse graphs, BFS/DFS, Dijkstra's, Prim's — default choice
Edge List — O(E) space, O(E) edge check:
- ›Build: flat list of (u, v, weight) tuples
- ›Sort by weight for Kruskal's: O(E log E)
- ›Best for: Kruskal's MST, Bellman-Ford, any algorithm iterating all edges
Conversion times:
- ›Matrix → List/Edges: O(V²) — must scan all cells
- ›List → Matrix/Edges: O(V + E)
- ›Edges → List/Matrix: O(V + E)
In the next topic you will explore Graph DFS — depth-first traversal, connected components, cycle detection, and topological sort.
An adjacency list for an undirected graph with V=5 vertices and E=7 edges stores how many total neighbour entries?