Adjacency List Representation in DSA Rust - Time & Space Complexity
We want to understand how fast we can build and use an adjacency list for a graph.
How does the time needed change when the graph grows bigger?
Analyze the time complexity of the following code snippet.
fn add_edge(adj_list: &mut Vec<Vec<usize>>, u: usize, v: usize) {
adj_list[u].push(v);
adj_list[v].push(u); // for undirected graph
}
fn build_graph(edges: &[(usize, usize)], n: usize) -> Vec<Vec<usize>> {
let mut adj_list = vec![Vec::new(); n];
for &(u, v) in edges {
add_edge(&mut adj_list, u, v);
}
adj_list
}
This code builds an adjacency list for an undirected graph with n nodes and a list of edges.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Looping through all edges to add connections.
- How many times: Once for each edge in the graph.
Each edge causes two insertions in the adjacency list. So the work grows with the number of edges.
| Input Size (n edges) | Approx. Operations |
|---|---|
| 10 | 20 insertions |
| 100 | 200 insertions |
| 1000 | 2000 insertions |
Pattern observation: Operations grow linearly with the number of edges.
Time Complexity: O(V + E)
This means the time grows with the number of nodes plus the number of edges in the graph.
[X] Wrong: "Adding edges takes O(V²) time because we have to check all nodes."
[OK] Correct: We directly add edges to the list for each node without scanning all nodes, so it depends on edges, not nodes squared.
Knowing adjacency list complexity helps you explain graph storage choices clearly and shows you understand efficient graph algorithms.
What if the graph was directed and we only added one direction per edge? How would the time complexity change?
Practice
Solution
Step 1: Understand graph representation
An adjacency list stores each node with a list of its neighbors, showing direct connections.Step 2: Compare options
Only A list where each node stores its connected neighbors describes this correctly; others describe different structures or concepts.Final Answer:
A list where each node stores its connected neighbors -> Option CQuick Check:
Adjacency list = neighbors list [OK]
- Confusing adjacency list with adjacency matrix
- Thinking adjacency list stores distances
- Mixing up with traversal queues
Solution
Step 1: Identify adjacency list structure
An adjacency list for nodes 0 to n-1 is a vector where each index holds a vector of neighbors.Step 2: Match Rust types
Vec matches this: outer Vec for nodes, inner Vec for neighbors. Others don't store neighbors per node properly.Final Answer:
Vec -> Option AQuick Check:
Vec of Vecs = adjacency list [OK]
- Using HashMap with single usize value instead of list
- Using Vec<usize> which holds only one neighbor list
- Using HashSet which doesn't map nodes to neighbors
let mut graph: Vec = vec![vec![], vec![], vec![]];
graph[0].push(1);
graph[1].push(2);
graph[2].push(0);
println!("{:?}", graph);Solution
Step 1: Trace graph initialization
graph starts as three empty vectors: [[], [], []]Step 2: Apply push operations
graph[0].push(1) -> [[1], [], []] graph[1].push(2) -> [[1], [2], []] graph[2].push(0) -> [[1], [2], [0]]Final Answer:
[[1], [2], [0]] -> Option BQuick Check:
Push neighbors correctly = [[1], [2], [0]] [OK]
- Mixing indices when pushing neighbors
- Assuming graph is empty after pushes
- Confusing order of neighbors
let mut graph: Vec = Vec::new(); graph[0].push(1);
Solution
Step 1: Analyze graph initialization
graph is empty Vec with no elements, so graph[0] does not exist.Step 2: Understand indexing error
Accessing graph[0] causes runtime panic because index 0 is out of bounds.Final Answer:
graph is empty, so graph[0] access causes panic -> Option DQuick Check:
Empty Vec indexing = panic [OK]
- Assuming Vec::new() has elements
- Confusing mutability with initialization
- Thinking push is invalid method
Solution
Step 1: Check graph initialization
let mut graph = vec![vec![], vec![], vec![], vec![]]; graph[0].push(1); graph[0].push(2); graph[2].push(3); initializes a Vec of Vecs with 4 empty vectors, ready to hold neighbors.Step 2: Verify edge additions
graph[0].push(1) and graph[0].push(2) add edges from node 0 to 1 and 2; graph[2].push(3) adds edge from 2 to 3.Step 3: Identify errors in other options
let mut graph = Vec::new(); graph[0].push(1); graph[0].push(2); graph[2].push(3);: graph is empty Vec, indexing causes panic. let mut graph = vec![0; 4]; graph[0] = 1; graph[0] = 2; graph[2] = 3;: graph is Vec<usize>, not Vec; push invalid. let mut graph = vec![vec![0]; 4]; graph[0].push(1); graph[0].push(2); graph[2].push(3);: graph initialized with vec![vec![0]; 4] means each node starts with neighbor 0, incorrect.Final Answer:
let mut graph = vec![vec![], vec![], vec![], vec![]]; graph[0].push(1); graph[0].push(2); graph[2].push(3); -> Option AQuick Check:
Vec of empty Vecs + push edges = correct [OK]
- Using empty Vec without size causes panic
- Initializing with repeated vec![0] adds wrong neighbors
- Using Vec<usize> instead of Vec
Ready to prove it?
Pass the test and this pattern moves unknown
