A graph is a collection of nodes (vertices) connected by edges.
Graphs model real-world relationships:
If trees are about hierarchy, graphs are about relationships.
Choosing the right representation affects performance and clarity.
Each node stores a list of its neighbors.
List<List<Integer>> graph = new ArrayList<>();
for(int i = 0; i < n; i++)
graph.add(new ArrayList<>());
graph.get(0).add(1);
graph.get(1).add(0); // undirected
Pros
Cons
Used in most interview problems.
2D matrix where:
matrix[i][j] = 1 → edge exists
int[][] matrix = new int[n][n];
matrix[0][1] = 1;
Pros
Cons
Used in dense graphs and Floyd-Warshall.
BFS explores nodes level by level using a queue.
void bfs(int start){
Queue<Integer> q = new LinkedList<>();
boolean[] vis = new boolean[n];
q.add(start);
vis[start] = true;
while(!q.isEmpty()){
int node = q.poll();
for(int nei : graph.get(node)){
if(!vis[nei]){
vis[nei] = true;
q.add(nei);
}
}
}
}
Time: O(V + E)
DFS explores as deep as possible before backtracking.
void dfs(int node){
vis[node] = true;
for(int nei : graph.get(node)){
if(!vis[nei])
dfs(nei);
}
}
Count separate subgraphs.
int components = 0;
for(int i = 0; i < n; i++){
if(!vis[i]){
dfs(i);
components++;
}
}
Idea:
boolean dfs(int node, int parent){
vis[node] = true;
for(int nei : graph.get(node)){
if(!vis[nei]){
if(dfs(nei, node)) return true;
} else if(nei != parent){
return true;
}
}
return false;
}
Idea:
boolean dfs(int node){
vis[node] = true;
stack[node] = true;
for(int nei : graph.get(node)){
if(!vis[nei] && dfs(nei)) return true;
else if(stack[nei]) return true;
}
stack[node] = false;
return false;
}
Linear ordering of nodes such that:
u → v ⇒ u comes before v
Only works for DAG.
Queue<Integer> q = new LinkedList<>();
int[] indeg = new int[n];
for(each edge u→v)
indeg[v]++;
for(int i = 0; i < n; i++)
if(indeg[i] == 0) q.add(i);
while(!q.isEmpty()){
int node = q.poll();
for(int nei : graph.get(node)){
indeg[nei]--;
if(indeg[nei] == 0)
q.add(nei);
}
}
Find shortest path from source to all nodes
Only works with non-negative weights
PriorityQueue<int[]> pq = new PriorityQueue<>(
(a, b) -> a[1] - b[1]
);
pq.add(new int[]{src, 0});
dist[src] = 0;
while(!pq.isEmpty()){
int[] cur = pq.poll();
int node = cur[0], d = cur[1];
for(Edge e : graph[node]){
if(dist[e.to] > d + e.wt){
dist[e.to] = d + e.wt;
pq.add(new int[]{e.to, dist[e.to]});
}
}
}
Time: O(E log V)
Handles negative weights
Detects negative cycles
for(int i = 1; i < V; i++){
for(Edge e : edges){
if(dist[e.u] + e.wt < dist[e.v])
dist[e.v] = dist[e.u] + e.wt;
}
}
Extra iteration checks for negative cycles.
Time: O(VE)
All-pairs shortest path
Uses DP
for(int k = 0; k < n; k++)
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++)
dist[i][j] = Math.min(dist[i][j],
dist[i][k] + dist[k][j]);
Time: O(n³)
A tree that:
Grows MST from a node
Uses Min Heap
PriorityQueue<Edge> pq = new PriorityQueue<>();
pq.add(new Edge(0, 0));
while(!pq.isEmpty()){
Edge e = pq.poll();
if(vis[e.to]) continue;
vis[e.to] = true;
for(Edge next : graph[e.to])
if(!vis[next.to])
pq.add(next);
}
Sorts edges by weight
Uses Union-Find
Collections.sort(edges);
for(Edge e : edges){
if(find(e.u) != find(e.v)){
union(e.u, e.v);
mst += e.wt;
}
}
Used to track components efficiently.
int find(int x){
if(parent[x] != x)
parent[x] = find(parent[x]);
return parent[x];
}
void union(int a, int b){
a = find(a);
b = find(b);
if(a != b)
parent[b] = a;
}
With path compression → almost O(1)
Graph that can be colored with 2 colors
No adjacent nodes share same color.
Queue<Integer> q = new LinkedList<>();
color[start] = 0;
while(!q.isEmpty()){
int node = q.poll();
for(int nei : graph.get(node)){
if(color[nei] == -1){
color[nei] = 1 - color[node];
q.add(nei);
} else if(color[nei] == color[node])
return false;
}
}
Grid problems are hidden graphs.
void dfs(int r, int c){
if(outOfBounds or grid[r][c] == 0) return;
grid[r][c] = 0;
dfs(r+1,c);
dfs(r-1,c);
dfs(r,c+1);
dfs(r,c-1);
}
Use queue with directions.
int[][] dir = {{1,0},{-1,0},{0,1},{0,-1}};