Graph Representation (Java)
Description
A graph is a non-linear data structure consisting of two fundamental components:
- Vertices (Nodes): Individual entities, typically labeled with integers starting from 0.
- Edges: Connections between pairs of vertices.
Graphs appear everywhere in computer science — modeling social networks (users connected by friendships), transportation networks (cities connected by roads), dependency systems (tasks connected by prerequisites), and much more.
There are two primary types of graphs:
- Undirected Graph: Every edge is bidirectional. If vertex A connects to vertex B, then B also connects to A. Example: a friendship — if Alice is friends with Bob, Bob is friends with Alice.
- Directed Graph (Digraph): Each edge has a specific direction. An edge from A to B does not imply an edge from B to A. Example: following someone on social media — you can follow someone without them following you back.
Edges can optionally carry weights — numeric values that represent cost, distance, capacity, or any other quantitative attribute.
The core challenge addressed here is: How do we store a graph in a Java program so that we can efficiently query and traverse it? The two standard representations are:
- Adjacency Matrix — a 2D array
int[][] adjwhereadj[i][j]indicates whether an edge exists from vertexito vertexj. - Adjacency List — an
ArrayList<ArrayList<Integer>>where each inner list holds the neighbors of the corresponding vertex.
Given V vertices (numbered 0 to V−1) and E edges, implement both representations in Java and understand when to use each one.

Examples
Example 1
Input: V = 3, E = 3, edges = [(0,1), (0,2), (1,2)], undirected
Output (Adjacency Matrix):
0 1 1
1 0 1
1 1 0
Output (Adjacency List):
0 → [1, 2]
1 → [0, 2]
2 → [0, 1]
Explanation: This graph forms a triangle where every vertex is connected to the other two. In the adjacency matrix, adj[0][1] = 1 and adj[1][0] = 1 both record the same undirected edge (0,1). The matrix is symmetric because every edge is bidirectional. In the adjacency list (using Java's ArrayList<ArrayList<Integer>>), adj.get(0) contains [1, 2] — the two neighbors of vertex 0.
Example 2
Input: V = 4, E = 4, edges = [(0,1), (1,2), (2,3), (3,0)], directed
Output (Adjacency Matrix):
0 1 0 0
0 0 1 0
0 0 0 1
1 0 0 0
Output (Adjacency List):
0 → [1]
1 → [2]
2 → [3]
3 → [0]
Explanation: This forms a directed cycle: 0→1→2→3→0. The adjacency matrix is not symmetric — adj[0][1] = 1 but adj[1][0] = 0, because the edge only goes from 0 to 1, not back. In the adjacency list, adj.get(0) only contains [1], not [1, 3] — even though vertex 3 points to vertex 0, that edge belongs in vertex 3's list, not vertex 0's.
Example 3
Input: V = 3, E = 3, edges = [(0,1,4), (0,2,7), (1,2,3)], undirected, weighted
Output (Adjacency Matrix):
0 4 7
4 0 3
7 3 0
Output (Adjacency List):
0 → [(1,4), (2,7)]
1 → [(0,4), (2,3)]
2 → [(0,7), (1,3)]
Explanation: In a weighted graph, the adjacency matrix stores the weight rather than 1. Cell adj[0][1] = 4 means the edge from vertex 0 to vertex 1 has weight 4. In the adjacency list using Java, we store int[] pairs or use a helper class: each entry holds both the neighbor and the weight. For example, vertex 0's list [(1,4), (2,7)] means vertex 0 connects to vertex 1 with weight 4 and to vertex 2 with weight 7.
Constraints
- 1 ≤ V ≤ 10^5 (number of vertices)
- 0 ≤ E ≤ min(V × (V − 1) / 2, 10^5) for undirected graphs
- 0 ≤ E ≤ min(V × (V − 1), 10^5) for directed graphs
- 0 ≤ u, v < V (vertex indices are 0-based)
- Edge weights (if present): −10^9 ≤ w ≤ 10^9
- No self-loops unless explicitly stated
- No duplicate edges in the input
Editorial
Adjacency Matrix
Intuition
The simplest way to represent a graph in code is with a two-dimensional grid. Picture a spreadsheet where both the row headers and column headers are vertex numbers (0, 1, 2, … V−1). Each cell answers one binary question: "Is there an edge from vertex i to vertex j?"
If the answer is yes, we write 1 (or the edge weight) in that cell. If not, the cell stays 0.
Think of a seating arrangement at a dinner party. You make a chart with all guest names along the top and down the side. If two guests know each other, you check the box where their row and column meet. This chart is your adjacency matrix.
For a graph with V vertices, we create a V × V two-dimensional integer array:
int[][] adj = new int[V][V];
In Java, new int[V][V] automatically initializes every cell to 0 — no manual initialization needed (unlike some other languages). For each edge (u, v):
- Undirected: Set
adj[u][v] = 1ANDadj[v][u] = 1 - Directed: Set only
adj[u][v] = 1 - Weighted: Store the weight:
adj[u][v] = weight
This representation makes checking if a specific edge exists extremely fast — it is a direct array access at adj[u][v], which takes constant O(1) time.
Step-by-Step Explanation
Let's build the adjacency matrix for Example 1: V = 3, edges = [(0,1), (0,2), (1,2)], undirected.
Step 1: Declare int[][] adj = new int[3][3];. Java initializes all cells to 0.
adj = [[0, 0, 0],
[0, 0, 0],
[0, 0, 0]]
Step 2: Process edge (0,1). Set adj[0][1] = 1.
adj = [[0, 1, 0],
[0, 0, 0],
[0, 0, 0]]
Step 3: Undirected mirror: set adj[1][0] = 1.
adj = [[0, 1, 0],
[1, 0, 0],
[0, 0, 0]]
Step 4: Process edge (0,2). Set adj[0][2] = 1.
adj = [[0, 1, 1],
[1, 0, 0],
[0, 0, 0]]
Step 5: Mirror: set adj[2][0] = 1.
adj = [[0, 1, 1],
[1, 0, 0],
[1, 0, 0]]
Step 6: Process edge (1,2). Set adj[1][2] = 1.
adj = [[0, 1, 1],
[1, 0, 1],
[1, 0, 0]]
Step 7: Mirror: set adj[2][1] = 1.
adj = [[0, 1, 1],
[1, 0, 1],
[1, 1, 0]]
Step 8: All 3 edges have been processed. The adjacency matrix is complete. The matrix is symmetric along the main diagonal (because the graph is undirected), and the diagonal is all zeros (no self-loops).
Building an Adjacency Matrix — Undirected Graph (Java) — Watch how each undirected edge fills two symmetric cells in the V×V integer array, gradually building the complete adjacency matrix.
Algorithm
- Read the number of vertices
Vand edgesE. - Create a
V × Vinteger array:int[][] adj = new int[V][V];(auto-initialized to 0 in Java). - For each edge
(u, v)read from input:- Set
adj[u][v] = 1(or the weight for weighted graphs). - If the graph is undirected, also set
adj[v][u] = 1. - If directed, set only
adj[u][v].
- Set
- To check if an edge exists from
itoj: readadj[i][j]— returns 1 if edge exists, 0 otherwise. - To find all neighbors of vertex
i: loop through column indexjfrom 0 to V−1, collecting everyjwhereadj[i][j] != 0.
Code
import java.util.Scanner;
public class Solution {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int V = sc.nextInt();
int E = sc.nextInt();
// Java auto-initializes int arrays to 0
int[][] adj = new int[V][V];
for (int i = 0; i < E; i++) {
int u = sc.nextInt();
int v = sc.nextInt();
adj[u][v] = 1;
adj[v][u] = 1; // Remove this line for directed graph
}
// Print the adjacency matrix
for (int i = 0; i < V; i++) {
for (int j = 0; j < V; j++) {
System.out.print(adj[i][j] + " ");
}
System.out.println();
}
}
}
// --- Weighted graph variant ---
// Store the weight instead of 1:
//
// int u = sc.nextInt(), v = sc.nextInt(), w = sc.nextInt();
// adj[u][v] = w;
// adj[v][u] = w; // Remove for directed#include <bits/stdc++.h>
using namespace std;
int main() {
int V, E;
cin >> V >> E;
// Create V x V matrix initialized to 0
vector<vector<int>> adj(V, vector<int>(V, 0));
for (int i = 0; i < E; i++) {
int u, v;
cin >> u >> v;
adj[u][v] = 1;
adj[v][u] = 1; // Remove for directed graph
}
// Print the adjacency matrix
for (int i = 0; i < V; i++) {
for (int j = 0; j < V; j++) {
cout << adj[i][j] << " ";
}
cout << endl;
}
return 0;
}def build_adjacency_matrix(V, edges, directed=False):
# Create V x V matrix initialized to 0
adj = [[0] * V for _ in range(V)]
for u, v in edges:
adj[u][v] = 1
if not directed:
adj[v][u] = 1 # Mirror for undirected
return adj
V, E = map(int, input().split())
edges = []
for _ in range(E):
u, v = map(int, input().split())
edges.append((u, v))
adj = build_adjacency_matrix(V, edges)
for row in adj:
print(" ".join(map(str, row)))Complexity Analysis
Time Complexity:
-
Building the matrix: O(V² + E)
new int[V][V]in Java allocates and zero-fills V² cells → O(V²).- Processing each of the E edges takes O(1) per edge → O(E).
- Total: O(V²) dominates.
-
Checking if edge (u, v) exists: O(1) — direct array access
adj[u][v]. -
Finding all neighbors of vertex u: O(V) — must scan all V cells in row
uto find non-zero entries.
Space Complexity: O(V²)
The 2D array always uses V² integer cells regardless of edge count. For a graph with 10,000 vertices and only 100 edges, we allocate 100,000,000 cells — 99.999% are wasted zeros. In Java, each int is 4 bytes, so a 10,000 × 10,000 matrix uses ~400 MB of memory.
Why This Approach Is Not Efficient
The adjacency matrix has a fundamental limitation: it always consumes O(V²) space, regardless of how sparse the graph is.
In practice, most graphs are sparse — they have far fewer edges than the maximum possible V × (V−1) / 2. Consider these numbers:
- V = 10,000, E = 20,000 (a typical sparse graph)
- Adjacency matrix: V² = 100,000,000 integer cells = ~400 MB in Java (4 bytes per int)
- Actual useful data: only 40,000 cells are non-zero (2 per undirected edge)
- 99.96% of the allocated memory is wasted
Besides wasted space, enumerating all neighbors of a vertex requires iterating over the full row of V cells, even if the vertex has just 2 neighbors. This O(V) neighbor scan is the bottleneck in graph traversal algorithms like BFS and DFS, which call it repeatedly.
The key observation: if we could store only the edges that exist instead of every possible edge, we would reduce space from O(V²) to O(V + E) and make neighbor iteration proportional to the actual number of neighbors. In Java, ArrayList<ArrayList<Integer>> gives us exactly this — dynamically-sized lists that only hold actual neighbors.

Optimal Approach - Adjacency List
Intuition
Instead of pre-allocating space for every possible connection, what if each vertex only stored the neighbors it actually has?
Imagine you maintain a contact list on your phone. You do not have an entry for every human on Earth — you only store the numbers of people you actually know. If you have 50 contacts, your list has 50 entries. If your friend has 200 contacts, their list has 200 entries.
The adjacency list works exactly like this. We create an array of V lists — one list per vertex. Each list holds only the actual neighbors of that vertex:
ArrayList<ArrayList<Integer>> adj = new ArrayList<>();
for (int i = 0; i < V; i++) {
adj.add(new ArrayList<>());
}
For each edge (u, v):
adj.get(u).add(v);— add v to u's neighbor list- For undirected:
adj.get(v).add(u);— add u to v's list as well
For weighted graphs, we store pairs of (neighbor, weight). In Java, a clean approach is:
ArrayList<ArrayList<int[]>> adj = new ArrayList<>();
// Each int[] is {neighbor, weight}
adj.get(u).add(new int[]{v, weight});
Or you can create a Pair class or use ArrayList<ArrayList<int[]>>.
This is the standard representation used in virtually all graph algorithm implementations in Java — BFS, DFS, Dijkstra, Kruskal, topological sort, and more all expect adjacency lists.
Step-by-Step Explanation
Let's build the adjacency list for Example 1: V = 3, edges = [(0,1), (0,2), (1,2)], undirected.
Step 1: Create 3 empty ArrayLists, one per vertex.
adj.get(0) = []
adj.get(1) = []
adj.get(2) = []
Step 2: Process edge (0,1). Call adj.get(0).add(1) — add vertex 1 to vertex 0's list.
adj.get(0) = [1]
adj.get(1) = []
adj.get(2) = []
Step 3: Undirected mirror: call adj.get(1).add(0) — add vertex 0 to vertex 1's list.
adj.get(0) = [1]
adj.get(1) = [0]
adj.get(2) = []
Step 4: Process edge (0,2). Call adj.get(0).add(2).
adj.get(0) = [1, 2]
adj.get(1) = [0]
adj.get(2) = []
Step 5: Mirror: call adj.get(2).add(0).
adj.get(0) = [1, 2]
adj.get(1) = [0]
adj.get(2) = [0]
Step 6: Process edge (1,2). Call adj.get(1).add(2).
adj.get(0) = [1, 2]
adj.get(1) = [0, 2]
adj.get(2) = [0]
Step 7: Mirror: call adj.get(2).add(1).
adj.get(0) = [1, 2]
adj.get(1) = [0, 2]
adj.get(2) = [0, 1]
Step 8: All edges processed. The adjacency list is complete. Each vertex stores only its actual neighbors. Total entries: 6 (two per undirected edge). To iterate over vertex 0's neighbors, just loop over adj.get(0) — instant access to [1, 2] without scanning any zeros.
Building an Adjacency List — Undirected Graph (Java) — Watch how each edge is processed and added to both vertices' ArrayLists, progressively building the graph's adjacency list representation.
Algorithm
- Read the number of vertices
Vand edgesE. - Create an
ArrayList<ArrayList<Integer>>with V empty inner lists:ArrayList<ArrayList<Integer>> adj = new ArrayList<>(); for (int i = 0; i < V; i++) adj.add(new ArrayList<>()); - For each edge
(u, v)from input:adj.get(u).add(v);— add v to u's neighbor list.- If undirected, also
adj.get(v).add(u);. - If directed, only add to
adj.get(u). - For weighted edges with weight
w:adj.get(u).add(new int[]{v, w});(usingArrayList<ArrayList<int[]>>).
- To iterate over all neighbors of vertex
i:for (int neighbor : adj.get(i)) { ... }. - To check if edge (u, v) exists: scan
adj.get(u)for valuev— takes O(degree(u)) time.
Code
import java.util.*;
public class Solution {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int V = sc.nextInt();
int E = sc.nextInt();
// Create adjacency list: V empty ArrayLists
ArrayList<ArrayList<Integer>> adj = new ArrayList<>();
for (int i = 0; i < V; i++) {
adj.add(new ArrayList<>());
}
for (int i = 0; i < E; i++) {
int u = sc.nextInt();
int v = sc.nextInt();
adj.get(u).add(v);
adj.get(v).add(u); // Remove for directed graph
}
// Print the adjacency list
for (int i = 0; i < V; i++) {
System.out.print(i + " -> ");
for (int neighbor : adj.get(i)) {
System.out.print(neighbor + " ");
}
System.out.println();
}
}
}
// --- Weighted graph variant ---
// Use ArrayList<ArrayList<int[]>> to store {neighbor, weight} pairs:
//
// ArrayList<ArrayList<int[]>> adj = new ArrayList<>();
// for (int i = 0; i < V; i++) adj.add(new ArrayList<>());
//
// int u = sc.nextInt(), v = sc.nextInt(), w = sc.nextInt();
// adj.get(u).add(new int[]{v, w});
// adj.get(v).add(new int[]{u, w}); // Remove for directed#include <bits/stdc++.h>
using namespace std;
int main() {
int V, E;
cin >> V >> E;
// Adjacency list: V empty vectors
vector<vector<int>> adj(V);
for (int i = 0; i < E; i++) {
int u, v;
cin >> u >> v;
adj[u].push_back(v);
adj[v].push_back(u); // Remove for directed graph
}
// Print the adjacency list
for (int i = 0; i < V; i++) {
cout << i << " -> ";
for (int neighbor : adj[i]) {
cout << neighbor << " ";
}
cout << endl;
}
return 0;
}
// --- Weighted variant ---
// vector<vector<pair<int,int>>> adj(V);
// adj[u].push_back({v, w});
// adj[v].push_back({u, w}); // Remove for directeddef build_adjacency_list(V, edges, directed=False):
# Create V empty lists
adj = [[] for _ in range(V)]
for u, v in edges:
adj[u].append(v)
if not directed:
adj[v].append(u) # Mirror for undirected
return adj
V, E = map(int, input().split())
edges = []
for _ in range(E):
u, v = map(int, input().split())
edges.append((u, v))
adj = build_adjacency_list(V, edges)
for i in range(V):
print(f"{i} -> {' '.join(map(str, adj[i]))}")
# --- Weighted variant ---
# Store tuples (neighbor, weight):
# def build_weighted_list(V, edges, directed=False):
# adj = [[] for _ in range(V)]
# for u, v, w in edges:
# adj[u].append((v, w))
# if not directed:
# adj[v].append((u, w))
# return adjComplexity Analysis
Time Complexity:
-
Building the list: O(V + E)
- Creating V empty ArrayLists takes O(V).
- Each edge is processed in O(1) amortized time (ArrayList
add()is amortized O(1) in Java). - Total: O(V + E).
-
Checking if edge (u, v) exists: O(degree(u))
- Must scan vertex u's neighbor list. In the worst case, degree can be V−1, but for sparse graphs it is typically much smaller.
- If O(1) edge checking is critical, you can additionally maintain a
HashSetper vertex.
-
Finding all neighbors of vertex u: O(degree(u))
- Just iterate over
adj.get(u). This visits only actual neighbors, not all V vertices.
- Just iterate over
Space Complexity: O(V + E)
- V ArrayList objects (one per vertex) → O(V).
- For undirected graphs, each edge appears in two lists → 2E total entries → O(E).
- For directed graphs, each edge appears once → E entries → O(E).
- Total: O(V + E).
For sparse graphs (E << V²), this is dramatically more efficient. A graph with V = 10,000 and E = 20,000 uses ~50,000 list entries vs. 100,000,000 matrix cells.
Summary — When to Use Which:
| Operation | Adjacency Matrix | Adjacency List |
|---|---|---|
| Space | O(V²) | O(V + E) |
| Check if edge exists | O(1) | O(degree) |
| List all neighbors | O(V) | O(degree) |
| Add an edge | O(1) | O(1) amortized |
| Best for | Dense graphs, O(1) edge queries | Sparse graphs, BFS/DFS, most algorithms |
| Java structure | int[][] | ArrayList<ArrayList<Integer>> |