Complete guide to graphs — representation, BFS, DFS, shortest path algorithms, topological sort, and classic graph problems in Java.
What is a Graph?
A graph G = (V, E) consists of vertices (nodes) and edges (connections). Unlike trees, graphs can have cycles, multiple paths, and no root.
| Undirected Graph | Directed Graph (Digraph): |
| 1 --- 2 1 | 2 |
| 3 --- 4 3 | 4 |
BFS (Breadth-First Search) — O(V + E)
public List<Integer> bfs(int start) {
List<Integer> order = new ArrayList<>();
boolean[] visited = new boolean[vertices];
Queue<Integer> queue = new LinkedList<>();
visited[start] = true;
queue.offer(start);
while (!queue.isEmpty()) {
int node = queue.poll();
order.add(node);
for (int[] neighbor : adjList.get(node)) {
if (!visited[neighbor[0]]) {
visited[neighbor[0]] = true;
queue.offer(neighbor[0]);
}
}
}
return order;
}
// Shortest path in unweighted graph
public int[] shortestPath(int source) {
int[] distance = new int[vertices];
Arrays.fill(distance, -1);
distance[source] = 0;
Queue<Integer> queue = new LinkedList<>();
queue.offer(source);
while (!queue.isEmpty()) {
int node = queue.poll();
for (int[] neighbor : adjList.get(node)) {
if (distance[neighbor[0]] == -1) {
distance[neighbor[0]] = distance[node] + 1;
queue.offer(neighbor[0]);
}
}
}
return distance;
}
DFS (Depth-First Search) — O(V + E)
public List<Integer> dfs(int start) {
List<Integer> order = new ArrayList<>();
boolean[] visited = new boolean[vertices];
dfsHelper(start, visited, order);
return order;
}
private void dfsHelper(int node, boolean[] visited, List<Integer> order) {
visited[node] = true;
order.add(node);
for (int[] neighbor : adjList.get(node)) {
if (!visited[neighbor[0]]) {
dfsHelper(neighbor[0], visited, order);
}
}
}
// Iterative DFS
public List<Integer> dfsIterative(int start) {
List<Integer> order = new ArrayList<>();
boolean[] visited = new boolean[vertices];
Stack<Integer> stack = new Stack<>();
stack.push(start);
while (!stack.isEmpty()) {
int node = stack.pop();
if (visited[node]) continue;
visited[node] = true;
order.add(node);
for (int[] neighbor : adjList.get(node)) {
if (!visited[neighbor[0]]) stack.push(neighbor[0]);
}
}
return order;
}
Dijkstra's Shortest Path — O((V+E) log V)
public int[] dijkstra(int source) {
int[] dist = new int[vertices];
Arrays.fill(dist, Integer.MAX_VALUE);
dist[source] = 0;
// Min-heap: [distance, vertex]
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);
pq.offer(new int[]{0, source});
while (!pq.isEmpty()) {
int[] curr = pq.poll();
int d = curr[0], u = curr[1];
if (d > dist[u]) continue; // Outdated entry
for (int[] edge : adjList.get(u)) {
int v = edge[0], weight = edge[1];
if (dist[u] + weight < dist[v]) {
dist[v] = dist[u] + weight;
pq.offer(new int[]{dist[v], v});
}
}
}
return dist;
}
Topological Sort (DAG only) — O(V + E)
// Kahn's Algorithm (BFS-based)
public List<Integer> topologicalSort() {
int[] indegree = new int[vertices];
for (int u = 0; u < vertices; u++)
for (int[] edge : adjList.get(u))
indegree[edge[0]]++;
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < vertices; i++)
if (indegree[i] == 0) queue.offer(i);
List<Integer> order = new ArrayList<>();
while (!queue.isEmpty()) {
int node = queue.poll();
order.add(node);
for (int[] neighbor : adjList.get(node)) {
if (--indegree[neighbor[0]] == 0) queue.offer(neighbor[0]);
}
}
if (order.size() != vertices) throw new RuntimeException("Cycle detected!");
return order;
}
Cycle Detection
// Undirected graph - using DFS
public boolean hasCycleUndirected() {
boolean[] visited = new boolean[vertices];
for (int i = 0; i < vertices; i++) {
if (!visited[i] && dfsCycleUndirected(i, -1, visited)) return true;
}
return false;
}
private boolean dfsCycleUndirected(int node, int parent, boolean[] visited) {
visited[node] = true;
for (int[] neighbor : adjList.get(node)) {
if (!visited[neighbor[0]]) {
if (dfsCycleUndirected(neighbor[0], node, visited)) return true;
} else if (neighbor[0] != parent) return true; // Back edge = cycle
}
return false;
}
// Directed graph - using colors (WHITE, GRAY, BLACK)
public boolean hasCycleDirected() {
int[] color = new int[vertices]; // 0=white, 1=gray, 2=black
for (int i = 0; i < vertices; i++) {
if (color[i] == 0 && dfsCycleDirected(i, color)) return true;
}
return false;
}
private boolean dfsCycleDirected(int node, int[] color) {
color[node] = 1; // Gray (being processed)
for (int[] neighbor : adjList.get(node)) {
if (color[neighbor[0]] == 1) return true; // Back edge to gray = cycle
if (color[neighbor[0]] == 0 && dfsCycleDirected(neighbor[0], color)) return true;
}
color[node] = 2; // Black (done)
return false;
}
Interview Questions
Q1: When to use BFS vs DFS?
Answer: BFS: shortest path in unweighted graphs, level-order processing, finding nearest node. DFS: topological sort, cycle detection, path finding, connected components, maze solving. BFS uses O(V) queue space; DFS uses O(V) stack space.
Q2: What is the difference between Dijkstra and BFS for shortest path?
Answer: BFS finds shortest path in unweighted graphs (all edges weight 1). Dijkstra handles weighted graphs with non-negative weights. BFS uses a regular queue; Dijkstra uses a priority queue.
Q3: How do you detect a cycle in a directed graph?
Answer: DFS with three states: WHITE (unvisited), GRAY (in current DFS path), BLACK (fully processed). A back edge to a GRAY node indicates a cycle. Alternatively, use Kahn's algorithm — if topological sort doesn't include all vertices, there's a cycle.
Q4: What is topological sorting and when is it used?
Answer: Linear ordering of vertices in a DAG such that for every edge u→v, u appears before v. Used for: task scheduling, build systems, course prerequisites, dependency resolution. Only possible for DAGs.
Q5: How do you find connected components?
Answer: Run DFS/BFS from each unvisited node. Each traversal discovers one connected component. For directed graphs, use Kosaraju's or Tarjan's algorithm to find strongly connected components.