Chapter 14: Graphs

Data Structures
Basic termsDegree, paths, cyclesAdjacency lists and matrices BFSDFSUnweighted shortest pathsDijkstraKruskal and Prim
Sercan KÜLCÜ · Giresun University · Computer Engineering

Introduction

Learning Outcomes

Basic terms

Explain vertices, edges, direction, weights, paths, and connectivity.

Representations

Compare adjacency matrices and adjacency lists by their costs.

Traversal and ordering

Run BFS, DFS, and topological sorting on DAGs, and interpret the results.

Shortest paths and MSTs

Match unweighted BFS, Dijkstra, Kruskal, and Prim to the problems they solve.

Basic Terms

Graphs

  • A graph is a set of vertices (or nodes) together with edges that connect pairs of them.
  • Graphs model complex relationships, networks, and structures.
  • Graph theory studies the relationships between vertices and edges.
0 1 2 3 4 edge vertex
A graph is a pair G = (V, E): V is the set of vertices, E the set of edges.
Example: V = {0, 1, 2, 3, 4}, E = {(0,1), (0,4), (1,2), (1,3), (1,4), (2,3), (3,4)} · n = |V| = 5, m = |E| = 7
Unlike a treeNo root, cycles allowed, and a vertex may be reachable along several paths.
Kind of relationA graph models many-to-many relationships.
Notationn = number of vertices, m = number of edges.

Basic Terms

Vertices (Nodes)

  • The basic building block of a graph.
  • Represents an object such as a city, a person, or a computer.
  • A vertex can store a value (a label, a name, a location…).
  • Example: cities and intersections on a map.
BLACK SEAMEDITERRANEANAEGEANEdirneIstanbulBursaIzmirEskişehirAnkaraAntalyaKonyaAdanaKayseriSamsunSivasGiresunTrabzonErzurumGaziantepDiyarbakırVan

Road map: cities are vertices, roads are edges

Once the map is a graph, "What is the shortest route from Istanbul to Van?" becomes a graph algorithm problem (Dijkstra's algorithm, for instance).

Basic Terms

Edges

  • Edges are the connections between vertices.
  • An edge represents a relationship between two vertices.
  • If (v, w) is an edge, v and w are adjacent: each is a neighbor of the other, and the edge is incident to both.
  • Examples: roads between cities, links in a computer network, friendships in a social network.

A social network: people are vertices, relationships are edges

Basic Terms

Degree

  • The degree of a vertex is the number of edges incident to it: deg(v).
  • A vertex of degree 0 is called isolated.
  • A self-loop adds 2 to the degree.
  • Example: the number of roads meeting at an intersection.
1 3 3 2 5 2 0 self-loop isolated vertex

Each vertex shows its own degree

Handshaking lemmaEvery edge adds one to the degree of each end: Σ deg(v) = 2mHere: 1+3+3+2+5+2+0 = 16 = 2·8 edges.
In a digraphin-degree: number of incoming arrows; out-degree: number of outgoing arrows.Σ in = Σ out = m

Basic Terms

Directed Graphs (Digraphs)

  • Each edge points in one direction, from one vertex to another: (u, v) ≠ (v, u).
  • There may be no way back: here, A cannot be reached from B.
  • A directed graph is also called a digraph.
  • Examples: web pages and their links, course prerequisites, one-way streets.
A B C E D F
VertexABCDEF
in-degree021111
out-degree111120

Basic Terms

Paths and Cycles

  • Path: a sequence of vertices in which each consecutive pair is joined by an edge.
  • Simple path: a path that repeats no vertex.
  • Cycle: a path that starts and ends at the same vertex.
  • Simple cycle: repeats no vertex except the start.
  • Graphs may have cycles; trees never do.
A B C D E F G H I J K L

Green: A-B-C-D-E-F-A · Red (dashed): G-H-I-J-K-L-G

Example: a flight route. Istanbul → Ankara → Izmir → Istanbul is a cycle; if it returns without visiting any city twice, it is a simple cycle.

Basic Terms

An Example Graph

1 2 3 4 5
  • V = {1, 2, 3, 4, 5}
  • E = {(1,2), (1,3), (1,4), (2,5), (3,4), (4,5)}
  • Vertices 1 and 2 are adjacent.
  • deg(1) = 3, deg(4) = 3, all others 2 → total 12 = 2·6 ✓
SequenceKind
1 → 2 → 5simple path
1 → 3 → 4 → 1simple cycle
1 → 3 → 4 → 1 → 2non-simple path (1 repeats)
1 → 3 → 4 → 1 → 4 → 1non-simple cycle

Basic Terms

Connected and Complete Graphs

  • Connected graph: there is a path between every pair of distinct vertices.
  • Connected component: a maximal group of vertices that can reach one another.
  • Complete graph: there is an edge between every pair of distinct vertices.
Connected: 1 component
Disconnected: 3 components
Complete graph K5: 10 edges

A simple undirected graph on n vertices has m ≤ n(n−1)/2, with equality for the complete graph. A connected graph has m ≥ n − 1.

Basic Terms

An Example Digraph

1 2 3 4 5
  • V = {1, 2, 3, 4, 5}
  • E = {(1,2), (1,4), (2,5), (4,5), (3,1), (4,3)}
  • Vertex 2 is adjacent to vertex 1 (there is an edge 1 → 2).
  • Vertex 1 is not adjacent to vertex 2!
SequenceKind
1 → 2 → 5directed path
1 → 4 → 3 → 1directed cycle
5 → …no edges leave 5

A directed edge (u, v) is an ordered pair: u is its tail (origin), v its head (destination).

Basic Terms

Weighted Graphs

  • Each edge carries a number, its weight: a distance, time, cost, capacity…
  • The length of a path is the sum of its edge weights. Weighted graphs can be undirected or directed.
8 10 3 6 5 7 1 2 3 4 5
Weighted undirected: path 3 → 4 → 1 is 5 + 3 = 8
8 3 6 7 10 5 1 2 3 4 5
Weighted directed: path 1 → 4 → 3 is 3 + 5 = 8
The direct edge is not always the shortest path: in the undirected graph, edge 1–3 weighs 10, but the path 1 → 4 → 3 is only 8.

Basic Terms

The Graph ADT

The core operations of Goodrich's Graph interface (V: vertex, E: edge):

OperationDescription
numVertices(), numEdges()Number of vertices n, number of edges m
vertices(), edges()Iterate over all vertices / all edges
getEdge(u, v)The edge from u to v, or null if there is none
endVertices(e), opposite(v, e)Both ends of an edge / the end of e other than v
outDegree(v), inDegree(v)Out-degree / in-degree (equal in an undirected graph)
outgoingEdges(v), incomingEdges(v)Edges leaving v / entering v
insertVertex(x), insertEdge(u, v, x)Add a new vertex / a new edge
removeVertex(v), removeEdge(e)Remove a vertex (with its incident edges) / an edge

What these operations cost depends on how the graph is stored in memory.

Graph Representations

Storing a Graph: Three Representations

Edge listAll edges in a single list.[(A,B), (A,E), …]
Adjacency listFor each vertex, a list of its neighbors.V[i] = {j, k, l}
Adjacency matrixAn n×n two-dimensional array; each cell says whether an edge exists.M[i][j] = 1
A B C D E

The edge list is the simplest representation:

(A, B)(A, E)(B, C)(B, E)(C, D)(D, E)
  • Space O(n + m); adding an edge is O(1).
  • But "Are u and v adjacent?" or "Who are u's neighbors?" means scanning the whole list: O(m).

Graph Representations

Adjacency Matrix

  • An n×n array: matrix[i][j] = 1 if there is an edge from vertex i to j, and 0 otherwise.
  • In a weighted graph, matrix[i][j] holds the edge weight, or ∞ (or some special value) if there is no edge.
  • In an undirected graph, the matrix is symmetric: matrix[i][j] == matrix[j][i].
  • Space is O(n²), so memory grows quickly with the number of vertices.
0 1 2 3 0 0 0 1 1 0 1 1 1 0 1 0 2 2 1 1 0 1 3 3 0 0 1 0 ← m[1][2]: one cell, O(1) ← neighbors of 2: whole row, O(n) symmetric: m[i][j] = m[j][i]

Easy

  • Is there an edge between two vertices? O(1)
  • Adding / removing an edge: O(1)

Hard

  • Finding a vertex's neighbors: a whole row, O(n)
  • In a sparse graph, most cells are empty (0)

Graph Representations

Adjacency Matrix: Three Examples

A B C D E
ABCDE
A01001
B10101
C01010
D00101
E11010

Undirected graph: each edge fills two cells, so the matrix is symmetric about the diagonal. Row sum = degree (B: 3).

A B C D E
ABCDE
A01001
B00110
C00000
D10101
E01010

Directed graph: row = origin, column = destination. Not symmetric; a row sum is an out-degree, a column sum an in-degree.

4 3 3 2 2 5 2 A B C D E
ABCDE
A04∞∞∞
B∞033∞
C2∞0∞2
D∞∞∞0∞
E∞5∞20

Weighted graph: a cell holds the weight, or ∞ if there is no edge (0 on the diagonal). Writing 0 in an empty cell could be confused with an edge of weight 0.

Graph Representations

Adjacency List

  • Keep n lists for n vertices (usually linked lists or dynamic arrays).
  • list[i] holds the vertices adjacent to vertex i (with weights, if the graph is weighted).
  • In an undirected graph, each edge (v, w) goes into both lists: w into v's list, v into w's.
  • Space is O(n + m), ideal for sparse graphs.
0 1 2 3 0 1 2 1 0 2 2 0 1 3 3 2 ← edge 2–3? scan the list: O(deg 2) 2m = 8 entries in all → space O(n + m)

Easy

  • Visiting a vertex's neighbors: O(deg(v))
  • Adding an edge: O(1)

Hard

  • Is there an edge between u and v? Scan the list: O(deg(u))

Graph Representations

Adjacency List: Three Examples

A B C D E
A B E B A C E C B D D C E E A B D

Undirected: 6 edges → 12 list entries (each edge twice). List length = degree.

A B C D E
A B E B C D C (empty) D A C E E B D

Directed: only outgoing edges are listed; 9 edges → 9 entries. No edge leaves C, so its list is empty.

4 3 3 2 2 5 2 A B C D E
A B 4 B C 3 D 3 C A 2 E 2 D (empty) E B 5 D 2

Weighted: each entry is a (neighbor, weight) pair; e.g. B's list: (C, 3), (D, 3).

Graph Representations

Representations: Memory and Operation Costs

Operation (n vertices, m edges)Edge listAdjacency listAdjacency matrix
MemoryO(n + m)O(n + m)O(n²)
Is there an edge u–v? getEdgeO(m)undirected: O(min(deg u, deg v))
directed u→v: O(outDeg u)
O(1)
Visit the neighbors of vO(m)O(deg v)O(n)
Sparse graph (m ≈ n)Road networks, social networks → adjacency list. With n = 10⁶ vertices, a matrix needs 10¹² cells!
Dense graph (m ≈ n²)Frequent "Is there an edge?" queries → adjacency matrix.

Graph Representations

An Adjacency List in Java

class Graph {
    private final int n;                                  // vertices: 0..n-1
    private final List<List<Integer>> adj = new ArrayList<>();
    Graph(int n) {
        this.n = n;
        for (int i = 0; i < n; i++)
            adj.add(new ArrayList<>());
    }
    void addEdge(int u, int v) {
        adj.get(u).add(v);
        adj.get(v).add(u);                  // omit this line in a digraph
    }
    List<Integer> neighbors(int u) { return adj.get(u); }   // O(1)
    int degree(int u) { return adj.get(u).size(); }
}
Matrix versionboolean[][] m = new boolean[n][n]; add an edge: m[u][v] = m[v][u] = true; query m[u][v] in O(1).
Weighted graphList entries are int[]{v, w} or a small Edge class; a matrix uses int[][] with Integer.MAX_VALUE for a missing edge.

Graph Representations · Step through the code

Code Trace: Building an Adjacency List

Graph(int n) {
  this.n = n;
  for (int i = 0; i < n; i++)
    adj.add(new ArrayList<>());
}
void addEdge(int u, int v) {
  adj.get(u).add(v);
  adj.get(v).add(u);  // not in a digraph
}
// main:
Graph g = new Graph(5);
int[][] E = {{0,1}, {0,4}, {1,2},
             {1,4}, {2,3}, {3,4}};
for (int[] e : E) g.addEdge(e[0], e[1]);
0 1 2 3 4 adj

new Graph(5): the constructor runs and sets n = 5. adj is still an empty outer list.

0 1 2 3 4 adj 0 [ ]

i = 0: add an empty inner list for vertex 0.

0 1 2 3 4 adj 0 [ ] 1 [ ]

i = 1: add an empty inner list for vertex 1.

0 1 2 3 4 adj 0 [ ] 1 [ ] 2 [ ]

i = 2: add an empty inner list for vertex 2.

0 1 2 3 4 adj 0 [ ] 1 [ ] 2 [ ] 3 [ ]

i = 3: add an empty inner list for vertex 3.

0 1 2 3 4 adj 0 [ ] 1 [ ] 2 [ ] 3 [ ] 4 [ ]

i = 4: add an empty inner list for vertex 4. Every vertex now has an (empty) neighbor list.

0 1 2 3 4 adj 0 1 1 [ ] 2 [ ] 3 [ ] 4 [ ]

Edge 0–1: 1 is appended to 0's list.

0 1 2 3 4 adj 0 1 1 0 2 [ ] 3 [ ] 4 [ ]

Undirected: the same edge is added in reverse → 0 goes into 1's list.

0 1 2 3 4 adj 0 1 4 1 0 2 [ ] 3 [ ] 4 [ ]

Edge 0–4: 4 is appended to 0's list.

0 1 2 3 4 adj 0 1 4 1 0 2 [ ] 3 [ ] 4 0

Undirected: the same edge is added in reverse → 0 goes into 4's list.

0 1 2 3 4 adj 0 1 4 1 0 2 2 [ ] 3 [ ] 4 0

Edge 1–2: 2 is appended to 1's list.

0 1 2 3 4 adj 0 1 4 1 0 2 2 1 3 [ ] 4 0

Undirected: the same edge is added in reverse → 1 goes into 2's list.

0 1 2 3 4 adj 0 1 4 1 0 2 4 2 1 3 [ ] 4 0

Edge 1–4: 4 is appended to 1's list.

0 1 2 3 4 adj 0 1 4 1 0 2 4 2 1 3 [ ] 4 0 1

Undirected: the same edge is added in reverse → 1 goes into 4's list.

0 1 2 3 4 adj 0 1 4 1 0 2 4 2 1 3 3 [ ] 4 0 1

Edge 2–3: 3 is appended to 2's list.

0 1 2 3 4 adj 0 1 4 1 0 2 4 2 1 3 3 2 4 0 1

Undirected: the same edge is added in reverse → 2 goes into 3's list.

0 1 2 3 4 adj 0 1 4 1 0 2 4 2 1 3 3 2 4 4 0 1

Edge 3–4: 4 is appended to 3's list.

0 1 2 3 4 adj 0 1 4 1 0 2 4 2 1 3 3 2 4 4 0 1 3

Undirected: the same edge is added in reverse → 3 goes into 4's list.

0 1 2 3 4 adj 0 1 4 1 0 2 4 2 1 3 3 2 4 4 0 1 3

Done: 6 edges → 2·6 = 12 list entries. Each addEdge is O(1); memory O(n + m). degree(1) = list length = 3.

Graph Representations · Step through the code

Code Trace: Building an Adjacency Matrix

0 1 2 3 4 0 1 2 3 4 0 0 0 0 0 0 1 0 0 0 0 0 2 0 0 0 0 0 3 0 0 0 0 0 4 0 0 0 0 0 m[row][col] (1 = true)

A 5×5 boolean matrix is created; Java initializes every cell to false (0). Memory O(n²) = 25 cells.

0 1 2 3 4 0 1 2 3 4 0 0 1 0 0 0 1 0 0 0 0 0 2 0 0 0 0 0 3 0 0 0 0 0 4 0 0 0 0 0 m[row][col] (1 = true)

Edge 0–1: m[0][1] = true (row 0, column 1).

0 1 2 3 4 0 1 2 3 4 0 0 1 0 0 0 1 1 0 0 0 0 2 0 0 0 0 0 3 0 0 0 0 0 4 0 0 0 0 0 m[row][col] (1 = true)

Mirror cell: m[1][0] = true. An undirected graph's matrix is symmetric about the diagonal.

0 1 2 3 4 0 1 2 3 4 0 0 1 0 0 1 1 1 0 0 0 0 2 0 0 0 0 0 3 0 0 0 0 0 4 0 0 0 0 0 m[row][col] (1 = true)

Edge 0–4: m[0][4] = true (row 0, column 4).

0 1 2 3 4 0 1 2 3 4 0 0 1 0 0 1 1 1 0 0 0 0 2 0 0 0 0 0 3 0 0 0 0 0 4 1 0 0 0 0 m[row][col] (1 = true)

Mirror cell: m[4][0] = true. An undirected graph's matrix is symmetric about the diagonal.

0 1 2 3 4 0 1 2 3 4 0 0 1 0 0 1 1 1 0 1 0 0 2 0 0 0 0 0 3 0 0 0 0 0 4 1 0 0 0 0 m[row][col] (1 = true)

Edge 1–2: m[1][2] = true (row 1, column 2).

0 1 2 3 4 0 1 2 3 4 0 0 1 0 0 1 1 1 0 1 0 0 2 0 1 0 0 0 3 0 0 0 0 0 4 1 0 0 0 0 m[row][col] (1 = true)

Mirror cell: m[2][1] = true. An undirected graph's matrix is symmetric about the diagonal.

0 1 2 3 4 0 1 2 3 4 0 0 1 0 0 1 1 1 0 1 0 1 2 0 1 0 0 0 3 0 0 0 0 0 4 1 0 0 0 0 m[row][col] (1 = true)

Edge 1–4: m[1][4] = true (row 1, column 4).

0 1 2 3 4 0 1 2 3 4 0 0 1 0 0 1 1 1 0 1 0 1 2 0 1 0 0 0 3 0 0 0 0 0 4 1 1 0 0 0 m[row][col] (1 = true)

Mirror cell: m[4][1] = true. An undirected graph's matrix is symmetric about the diagonal.

0 1 2 3 4 0 1 2 3 4 0 0 1 0 0 1 1 1 0 1 0 1 2 0 1 0 1 0 3 0 0 0 0 0 4 1 1 0 0 0 m[row][col] (1 = true)

Edge 2–3: m[2][3] = true (row 2, column 3).

0 1 2 3 4 0 1 2 3 4 0 0 1 0 0 1 1 1 0 1 0 1 2 0 1 0 1 0 3 0 0 1 0 0 4 1 1 0 0 0 m[row][col] (1 = true)

Mirror cell: m[3][2] = true. An undirected graph's matrix is symmetric about the diagonal.

0 1 2 3 4 0 1 2 3 4 0 0 1 0 0 1 1 1 0 1 0 1 2 0 1 0 1 0 3 0 0 1 0 1 4 1 1 0 0 0 m[row][col] (1 = true)

Edge 3–4: m[3][4] = true (row 3, column 4).

0 1 2 3 4 0 1 2 3 4 0 0 1 0 0 1 1 1 0 1 0 1 2 0 1 0 1 0 3 0 0 1 0 1 4 1 1 0 1 0 m[row][col] (1 = true)

Mirror cell: m[4][3] = true. An undirected graph's matrix is symmetric about the diagonal.

0 1 2 3 4 0 1 2 3 4 0 0 1 0 0 1 1 1 0 1 0 1 2 0 1 0 1 0 3 0 0 1 0 1 4 1 1 0 1 0 m[row][col] (1 = true)

Done: 12 cells are true, 13 are false and wasted. "Is there an edge 2–4?" → one access to m[2][4], O(1); but finding the neighbors of 2 means scanning a whole row: O(n).

boolean[][] m = new boolean[n][n];
void addEdge(int u, int v) {
  m[u][v] = true;
  m[v][u] = true;  // not in a digraph
}
// main: same 6 edges (n = 5)
for (int[] e : E) g.addEdge(e[0], e[1]);

Traversal (BFS / DFS)

Traversal Algorithms

A traversal systematically visits every vertex reachable from a start vertex, each exactly once.

Breadth-First Search (BFS)First all the nearby neighbors, then their neighbors… BFS advances level by level.helper structure: queue (FIFO)
Depth-First Search (DFS)DFS follows one path as deep as it can and backs up at a dead end.helper structure: stack (LIFO) / recursion
Unlike a tree, a graph can have cycles, and a vertex can be reached along several paths, so a visited mark (e.g. boolean[] visited) is essential; without it the algorithm can loop forever.

Traversal (BFS / DFS)

Breadth-First Search (BFS)

  • Explores vertices level by level: first those 1 edge from the start, then those 2 edges away…
  • Uses a queue: first discovered, first processed.
  • Each vertex is visited exactly once.
level 0 level 1 level 2 s a b c d e 1 2 3 4 5 6

Number = visit order; bold = BFS tree

Pros

  • Finds shortest paths (fewest edges) in unweighted graphs.
  • Discovers nearby vertices quickly.

Con

  • Can use a lot of memory: a whole level waits in the queue.
Examples: "friends of my friends" in a social network (level 2); a web crawler exploring a site layer by layer.

Traversal (BFS / DFS)

The BFS Algorithm

Pseudocode
BFS(start):
  queue.add(start)
  mark start visited
  while queue is not empty:
    w = queue.remove()
    for each unvisited
        neighbor u of w:
      mark u visited
      queue.add(u)

A vertex is marked when it enters the queue, so no vertex enters the queue twice.

Java
void bfs(int s) {
  boolean[] visited = new boolean[n];
  Queue<Integer> q = new ArrayDeque<>();
  visited[s] = true;  q.add(s);
  while (!q.isEmpty()) {
    int w = q.poll();
    System.out.print(w + " ");
    for (int u : adj.get(w))
      if (!visited[u]) {
        visited[u] = true;
        q.add(u);
      }
  }
}

Traversal (BFS / DFS) · Step through the code

Code Trace: bfs(A)

void bfs(int s) {
  boolean[] visited = new boolean[n];
  Queue<Integer> q = new ArrayDeque<>();
  visited[s] = true;  q.add(s);
  while (!q.isEmpty()) {
    int w = q.poll();
    System.out.print(w + " ");
    for (int u : adj.get(w))
      if (!visited[u]) {
        visited[u] = true;
        q.add(u);
      }
  }
}
I A B C D H G F E q (front → back) (empty) visited[] A F B F C F D F E F F F G F H F I F unseen queued current done

s = A (A=0, B=1, …, I=8; neighbors in alphabetical order). visited is all false; the queue is empty.

I A B C D H G F E q (front → back) A visited[] A T B F C F D F E F F F G F H F I F unseen queued current done

A is marked as it enters the queue: visited[A] = true, q = [A].

I A B C D H G F E q (front → back) (empty) visited[] A T B F C F D F E F F F G F H F I F unseen queued current done

Poll A from the front of the queue and print it. Now scan its neighbors B, F, I.

I A B C D H G F E q (front → back) B visited[] A T B T C F D F E F F F G F H F I F unseen queued current done

u = B is unvisited → mark it and add it to the back of the queue (level 1).

I A B C D H G F E q (front → back) B F visited[] A T B T C F D F E F F T G F H F I F unseen queued current done

u = F is unvisited → mark it and add it to the back of the queue (level 1).

I A B C D H G F E q (front → back) B F I visited[] A T B T C F D F E F F T G F H F I T unseen queued current done

u = I is unvisited → mark it and add it to the back of the queue (level 1).

I A B C D H G F E q (front → back) F I visited[] A T B T C F D F E F F T G F H F I T unseen queued current done

Poll B from the front of the queue and print it. Now scan its neighbors A, C, E.

I A B C D H G F E q (front → back) F I visited[] A T B T C F D F E F F T G F H F I T unseen queued current done

u = A: already visited → skip. Without the visited mark, A would enter the queue again.

I A B C D H G F E q (front → back) F I C visited[] A T B T C T D F E F F T G F H F I T unseen queued current done

u = C is unvisited → mark it and add it to the back of the queue (level 2).

I A B C D H G F E q (front → back) F I C E visited[] A T B T C T D F E T F T G F H F I T unseen queued current done

u = E is unvisited → mark it and add it to the back of the queue (level 2).

I A B C D H G F E q (front → back) I C E visited[] A T B T C T D F E T F T G F H F I T unseen queued current done

Poll F from the front of the queue and print it. Now scan its neighbors A, G.

I A B C D H G F E q (front → back) I C E visited[] A T B T C T D F E T F T G F H F I T unseen queued current done

u = A: already visited → skip.

I A B C D H G F E q (front → back) I C E G visited[] A T B T C T D F E T F T G T H F I T unseen queued current done

u = G is unvisited → mark it and add it to the back of the queue (level 2).

I A B C D H G F E q (front → back) C E G visited[] A T B T C T D F E T F T G T H F I T unseen queued current done

Poll I from the front of the queue and print it. Now scan its neighbor A.

I A B C D H G F E q (front → back) C E G visited[] A T B T C T D F E T F T G T H F I T unseen queued current done

u = A: already visited → skip.

Traversal (BFS / DFS) · Step through the code

Code Trace: bfs(A) (continued)

void bfs(int s) {
  boolean[] visited = new boolean[n];
  Queue<Integer> q = new ArrayDeque<>();
  visited[s] = true;  q.add(s);
  while (!q.isEmpty()) {
    int w = q.poll();
    System.out.print(w + " ");
    for (int u : adj.get(w))
      if (!visited[u]) {
        visited[u] = true;
        q.add(u);
      }
  }
}
I A B C D H G F E q (front → back) E G visited[] A T B T C T D F E T F T G T H F I T unseen queued current done

Poll C from the front of the queue and print it. Now scan its neighbors B, D, E.

I A B C D H G F E q (front → back) E G visited[] A T B T C T D F E T F T G T H F I T unseen queued current done

u = B: already visited → skip.

I A B C D H G F E q (front → back) E G D visited[] A T B T C T D T E T F T G T H F I T unseen queued current done

u = D is unvisited → mark it and add it to the back of the queue (level 3).

I A B C D H G F E q (front → back) E G D visited[] A T B T C T D T E T F T G T H F I T unseen queued current done

u = E: already visited → skip.

I A B C D H G F E q (front → back) G D visited[] A T B T C T D T E T F T G T H F I T unseen queued current done

Poll E from the front of the queue and print it. Now scan its neighbors B, C, G.

I A B C D H G F E q (front → back) G D visited[] A T B T C T D T E T F T G T H F I T unseen queued current done

u = B, C, G: all already visited → skip.

I A B C D H G F E q (front → back) D visited[] A T B T C T D T E T F T G T H F I T unseen queued current done

Poll G from the front of the queue and print it. Now scan its neighbors D, E, F.

I A B C D H G F E q (front → back) D visited[] A T B T C T D T E T F T G T H F I T unseen queued current done

u = D, E, F: all already visited → skip.

I A B C D H G F E q (front → back) (empty) visited[] A T B T C T D T E T F T G T H F I T unseen queued current done

Poll D from the front of the queue and print it. Now scan its neighbors C, G, H.

I A B C D H G F E q (front → back) (empty) visited[] A T B T C T D T E T F T G T H F I T unseen queued current done

u = C, G: all already visited → skip.

I A B C D H G F E q (front → back) H visited[] A T B T C T D T E T F T G T H T I T unseen queued current done

u = H is unvisited → mark it and add it to the back of the queue (level 4).

I A B C D H G F E q (front → back) (empty) visited[] A T B T C T D T E T F T G T H T I T unseen queued current done

Poll H from the front of the queue and print it. Now scan its neighbor D.

I A B C D H G F E q (front → back) (empty) visited[] A T B T C T D T E T F T G T H T I T unseen queued current done

u = D: already visited → skip.

I A B C D H G F E q (front → back) (empty) visited[] A T B T C T D T E T F T G T H T I T unseen queued current done

The queue is empty → done. Order A B F I C E G D H: level by level. Each vertex enters the queue once and each list is scanned once → O(n + m).

Traversal (BFS / DFS)

Depth-First Search (DFS)

  • Explores vertices along a path, going as deep as it can.
  • At a dead end (no unvisited neighbors left) it backtracks and tries another branch.
  • Uses a stack or recursive calls.
s a b c d e 1 2 3 4 5 6

s→a→d→b goes deep; back up at the dead end, then c→e

Pros

  • Usually uses little memory (only the current path is on the stack).
  • Follows a path all the way to its end.

Con

  • Does not guarantee shortest paths.
Uses: solving mazes, detecting cycles, finding connected components, and topological sorting of directed acyclic graphs.

Traversal (BFS / DFS)

The DFS Algorithm

Recursive
void dfs(int v, boolean[] visited) {
  visited[v] = true;
  System.out.print(v + " ");
  for (int u : adj.get(v))
    if (!visited[u])
      dfs(u, visited);
}

Recursion keeps the stack for us, as the call stack.

With an explicit stack
void dfsStack(int s) {
  boolean[] visited = new boolean[n];
  Deque<Integer> stack = new ArrayDeque<>();
  stack.push(s);  visited[s] = true;
  while (!stack.isEmpty()) {
    int v = stack.peek();
    Integer next = null;
    for (int u : adj.get(v))
      if (!visited[u]) { next = u; break; }
    if (next == null) stack.pop(); // back
    else { visited[next] = true;
           stack.push(next); }
  }
}

Traversal (BFS / DFS) · Step through the code

Code Trace: dfs(A) (recursive)

void dfs(int v, boolean[] visited) {
  visited[v] = true;
  System.out.print(v + " ");
  for (int u : adj.get(v))
    if (!visited[u])
      dfs(u, visited);
}
I A B C D H G F E visited[] A T B F C F D F E F F F G F H F I F unseen on stack on top finished

dfs(A, visited) is called (A=0, B=1, …, I=8; neighbors in alphabetical order). A is marked and printed.

I A B C D H G F E visited[] A T B T C F D F E F F F G F H F I F unseen on stack on top finished

Neighbor B of A is unvisited → call dfs(B): a new frame opens; B is marked and printed.

I A B C D H G F E visited[] A T B T C F D F E F F F G F H F I F unseen on stack on top finished

B's neighbor A is already visited → skip.

I A B C D H G F E visited[] A T B T C T D F E F F F G F H F I F unseen on stack on top finished

Neighbor C of B is unvisited → call dfs(C): a new frame opens; C is marked and printed.

I A B C D H G F E visited[] A T B T C T D F E F F F G F H F I F unseen on stack on top finished

C's neighbor B is already visited → skip.

I A B C D H G F E visited[] A T B T C T D T E F F F G F H F I F unseen on stack on top finished

Neighbor D of C is unvisited → call dfs(D): a new frame opens; D is marked and printed.

I A B C D H G F E visited[] A T B T C T D T E F F F G F H F I F unseen on stack on top finished

D's neighbor C is already visited → skip.

I A B C D H G F E visited[] A T B T C T D T E F F F G T H F I F unseen on stack on top finished

Neighbor G of D is unvisited → call dfs(G): a new frame opens; G is marked and printed.

I A B C D H G F E visited[] A T B T C T D T E F F F G T H F I F unseen on stack on top finished

G's neighbor D is already visited → skip.

I A B C D H G F E visited[] A T B T C T D T E T F F G T H F I F unseen on stack on top finished

Neighbor E of G is unvisited → call dfs(E): a new frame opens; E is marked and printed.

I A B C D H G F E visited[] A T B T C T D T E T F F G T H F I F unseen on stack on top finished

E's neighbors B, C, G are already visited → skip.

I A B C D H G F E visited[] A T B T C T D T E T F F G T H F I F unseen on stack on top finished

E has no neighbors left to visit → dfs(E) returns and its frame is popped (backtrack).

I A B C D H G F E visited[] A T B T C T D T E T F T G T H F I F unseen on stack on top finished

Neighbor F of G is unvisited → call dfs(F): a new frame opens; F is marked and printed.

I A B C D H G F E visited[] A T B T C T D T E T F T G T H F I F unseen on stack on top finished

F's neighbors A, G are already visited → skip.

I A B C D H G F E visited[] A T B T C T D T E T F T G T H F I F unseen on stack on top finished

F has no neighbors left to visit → dfs(F) returns and its frame is popped (backtrack).

Traversal (BFS / DFS) · Step through the code

Code Trace: dfs(A) (recursive, continued)

void dfs(int v, boolean[] visited) {
  visited[v] = true;
  System.out.print(v + " ");
  for (int u : adj.get(v))
    if (!visited[u])
      dfs(u, visited);
}
I A B C D H G F E visited[] A T B T C T D T E T F T G T H F I F unseen on stack on top finished

G has no neighbors left to visit → dfs(G) returns and its frame is popped (backtrack).

I A B C D H G F E visited[] A T B T C T D T E T F T G T H T I F unseen on stack on top finished

Neighbor H of D is unvisited → call dfs(H): a new frame opens; H is marked and printed.

I A B C D H G F E visited[] A T B T C T D T E T F T G T H T I F unseen on stack on top finished

H's neighbor D is already visited → skip.

I A B C D H G F E visited[] A T B T C T D T E T F T G T H T I F unseen on stack on top finished

H has no neighbors left to visit → dfs(H) returns and its frame is popped (backtrack).

I A B C D H G F E visited[] A T B T C T D T E T F T G T H T I F unseen on stack on top finished

D has no neighbors left to visit → dfs(D) returns and its frame is popped (backtrack).

I A B C D H G F E visited[] A T B T C T D T E T F T G T H T I F unseen on stack on top finished

C's neighbor E is already visited → skip.

I A B C D H G F E visited[] A T B T C T D T E T F T G T H T I F unseen on stack on top finished

C has no neighbors left to visit → dfs(C) returns and its frame is popped (backtrack).

I A B C D H G F E visited[] A T B T C T D T E T F T G T H T I F unseen on stack on top finished

B's neighbor E is already visited → skip.

I A B C D H G F E visited[] A T B T C T D T E T F T G T H T I F unseen on stack on top finished

B has no neighbors left to visit → dfs(B) returns and its frame is popped (backtrack).

I A B C D H G F E visited[] A T B T C T D T E T F T G T H T I F unseen on stack on top finished

A's neighbor F is already visited → skip.

I A B C D H G F E visited[] A T B T C T D T E T F T G T H T I T unseen on stack on top finished

Neighbor I of A is unvisited → call dfs(I): a new frame opens; I is marked and printed.

I A B C D H G F E visited[] A T B T C T D T E T F T G T H T I T unseen on stack on top finished

I's neighbor A is already visited → skip.

I A B C D H G F E visited[] A T B T C T D T E T F T G T H T I T unseen on stack on top finished

I has no neighbors left to visit → dfs(I) returns and its frame is popped (backtrack).

I A B C D H G F E visited[] A T B T C T D T E T F T G T H T I T unseen on stack on top finished

dfs(A) returns and the stack is empty. Order: A B C D G E F H I.

Traversal (BFS / DFS) · Step through the code

Code Trace: dfsStack(A)

void dfsStack(int s) {
  boolean[] visited = new boolean[n];
  Deque<Integer> stack = new ArrayDeque<>();
  stack.push(s);  visited[s] = true;
  while (!stack.isEmpty()) {
    int v = stack.peek();
    Integer next = null;
    for (int u : adj.get(v))
      if (!visited[u]) { next = u; break; }
    if (next == null) stack.pop(); // back
    else { visited[next] = true;
           stack.push(next); }
  }
}
I A B C D H G F E stack (bottom → top) (empty) visited[] A F B F C F D F E F F F G F H F I F unseen on stack on top finished

Start s = A. visited is all false; the stack is empty.

I A B C D H G F E stack (bottom → top) A visited[] A T B F C F D F E F F F G F H F I F unseen on stack on top finished

A is pushed and marked. The top of the stack = the vertex we are at.

I A B C D H G F E stack (bottom → top) A B visited[] A T B T C F D F E F F F G F H F I F unseen on stack on top finished

peek = A; first unvisited neighbor B (break) → mark it and push it.

I A B C D H G F E stack (bottom → top) A B C visited[] A T B T C T D F E F F F G F H F I F unseen on stack on top finished

peek = B; first unvisited neighbor C (break) → mark it and push it.

I A B C D H G F E stack (bottom → top) A B C D visited[] A T B T C T D T E F F F G F H F I F unseen on stack on top finished

peek = C; first unvisited neighbor D (break) → mark it and push it.

I A B C D H G F E stack (bottom → top) A B C D G visited[] A T B T C T D T E F F F G T H F I F unseen on stack on top finished

peek = D; first unvisited neighbor G (break) → mark it and push it.

I A B C D H G F E stack (bottom → top) A B C D G E visited[] A T B T C T D T E T F F G T H F I F unseen on stack on top finished

peek = G; first unvisited neighbor E (break) → mark it and push it.

I A B C D H G F E stack (bottom → top) A B C D G visited[] A T B T C T D T E T F F G T H F I F unseen on stack on top finished

peek = E; no unvisited neighbor (next = null) → pop and back up.

I A B C D H G F E stack (bottom → top) A B C D G F visited[] A T B T C T D T E T F T G T H F I F unseen on stack on top finished

peek = G; first unvisited neighbor F (break) → mark it and push it.

I A B C D H G F E stack (bottom → top) A B C D G visited[] A T B T C T D T E T F T G T H F I F unseen on stack on top finished

peek = F; no unvisited neighbor (next = null) → pop and back up.

I A B C D H G F E stack (bottom → top) A B C D visited[] A T B T C T D T E T F T G T H F I F unseen on stack on top finished

peek = G; no unvisited neighbor (next = null) → pop and back up.

I A B C D H G F E stack (bottom → top) A B C D H visited[] A T B T C T D T E T F T G T H T I F unseen on stack on top finished

peek = D; first unvisited neighbor H (break) → mark it and push it.

I A B C D H G F E stack (bottom → top) A B C D visited[] A T B T C T D T E T F T G T H T I F unseen on stack on top finished

peek = H; no unvisited neighbor (next = null) → pop and back up.

I A B C D H G F E stack (bottom → top) A B C visited[] A T B T C T D T E T F T G T H T I F unseen on stack on top finished

peek = D; no unvisited neighbor (next = null) → pop and back up.

I A B C D H G F E stack (bottom → top) A B visited[] A T B T C T D T E T F T G T H T I F unseen on stack on top finished

peek = C; no unvisited neighbor (next = null) → pop and back up.

I A B C D H G F E stack (bottom → top) A visited[] A T B T C T D T E T F T G T H T I F unseen on stack on top finished

peek = B; no unvisited neighbor (next = null) → pop and back up.

I A B C D H G F E stack (bottom → top) A I visited[] A T B T C T D T E T F T G T H T I T unseen on stack on top finished

peek = A; first unvisited neighbor I (break) → mark it and push it.

I A B C D H G F E stack (bottom → top) A visited[] A T B T C T D T E T F T G T H T I T unseen on stack on top finished

peek = I; no unvisited neighbor (next = null) → pop and back up.

I A B C D H G F E stack (bottom → top) (empty) visited[] A T B T C T D T E T F T G T H T I T unseen on stack on top finished

peek = A; no unvisited neighbor (next = null) → pop and back up. The stack is empty: done. The visit order matches the recursive version.

Traversal (BFS / DFS)

BFS vs. DFS

BFSDFS
Helper structureQueue (FIFO)Stack (LIFO) / recursion
Time (adjacency list)O(n + m)O(n + m)
Extra memoryO(n): the widest level is in the queueO(n): the longest path is on the stack
Shortest paths?Yes (in unweighted graphs)No
Typical usesfewest steps, finding levelscycle detection, components, mazes
Why O(n + m)? Each vertex is processed once and every adjacency entry is scanned. In an undirected graph each edge appears in two lists, but 2m is still O(m).

The traces assume neighbors are visited in alphabetical order; a different neighbor order gives a different visit order. Because dfsStack rescans the neighbor list from the start on every peek, it can take longer than O(n + m); remembering where each vertex left off restores O(n + m).

Traversal (BFS / DFS) · Step through the code

Code Trace: Numbering Components with DFS

0 1 2 3 4 5 v comp -1 0 -1 1 -1 2 -1 3 -1 4 -1 5

Every vertex is −1 (not yet in any component).

0 1 2 3 4 5 v comp -1 0 -1 1 -1 2 -1 3 -1 4 -1 5

Component counter count = 0.

0 1 2 3 4 5 v comp -1 0 -1 1 -1 2 -1 3 -1 4 -1 5

comp[0] = −1: new component. count++ yields 0 and count becomes 1; call dfs(0, 0).

0 1 2 3 4 5 v comp 0 0 -1 1 -1 2 -1 3 -1 4 -1 5

comp[0] = 0: vertex 0 gets component number 0.

0 1 2 3 4 5 v comp 0 0 0 1 -1 2 -1 3 -1 4 -1 5

comp[1] = 0: vertex 1 gets component number 0.

0 1 2 3 4 5 v comp 0 0 0 1 -1 2 -1 3 -1 4 -1 5

Neighbor 0 is already labeled (comp = 0) → skip. Each edge is examined twice: O(n + m).

0 1 2 3 4 5 v comp 0 0 0 1 0 2 -1 3 -1 4 -1 5

comp[2] = 0: vertex 2 gets component number 0.

0 1 2 3 4 5 v comp 0 0 0 1 0 2 -1 3 -1 4 -1 5

Neighbor 0 is already labeled (comp = 0) → skip. Each edge is examined twice: O(n + m).

0 1 2 3 4 5 v comp 0 0 0 1 0 2 -1 3 -1 4 -1 5

comp[1] = 0: vertex 1 was labeled by an earlier DFS → skip.

0 1 2 3 4 5 v comp 0 0 0 1 0 2 -1 3 -1 4 -1 5

comp[2] = 0: vertex 2 was labeled by an earlier DFS → skip.

0 1 2 3 4 5 v comp 0 0 0 1 0 2 -1 3 -1 4 -1 5

comp[3] = −1: new component. count++ yields 1 and count becomes 2; call dfs(3, 1).

0 1 2 3 4 5 v comp 0 0 0 1 0 2 1 3 -1 4 -1 5

comp[3] = 1: vertex 3 gets component number 1.

0 1 2 3 4 5 v comp 0 0 0 1 0 2 1 3 1 4 -1 5

comp[4] = 1: vertex 4 gets component number 1.

0 1 2 3 4 5 v comp 0 0 0 1 0 2 1 3 1 4 -1 5

Neighbor 3 is already labeled (comp = 1) → skip. Each edge is examined twice: O(n + m).

0 1 2 3 4 5 v comp 0 0 0 1 0 2 1 3 1 4 -1 5

comp[4] = 1: vertex 4 was labeled by an earlier DFS → skip.

0 1 2 3 4 5 v comp 0 0 0 1 0 2 1 3 1 4 -1 5

comp[5] = −1: new component. count++ yields 2 and count becomes 3; call dfs(5, 2).

0 1 2 3 4 5 v comp 0 0 0 1 0 2 1 3 1 4 2 5

comp[5] = 2: vertex 5 gets component number 2.

0 1 2 3 4 5 v comp 0 0 0 1 0 2 1 3 1 4 2 5

3 components: {0, 1, 2}, {3, 4}, {5}. Same comp = same component; the query comp[u] == comp[v] is O(1).

int components(int[] comp) {
  Arrays.fill(comp, -1);
  int count = 0;
  for (int s = 0; s < n; s++)
    if (comp[s] == -1) dfs(s, count++, comp);
  return count;
}
void dfs(int v, int c, int[] comp) {
  comp[v] = c;
  for (int u : adj.get(v))
    if (comp[u] == -1) dfs(u, c, comp);
}

Traversal (BFS / DFS)

Testing Whether a Graph Is Bipartite

  • Bipartite: the vertices split into two groups, and every edge runs between the groups.
  • Test = 2-coloring with BFS: give each neighbor the opposite color; two adjacent vertices of the same color → not bipartite.
  • Equivalently: no odd-length cycle.
0 1 2 3 4 5 0 1 2 bipartite ✓ (even cycles) triangle ✗: 1 and 2 same color
// color[] starts at -1; BFS from s
color[s] = 0;  q.add(s);
while (!q.isEmpty()) {
  int w = q.poll();
  for (int u : adj.get(w))
    if (color[u] == -1) {
      color[u] = 1 - color[w];  q.add(u);
    } else if (color[u] == color[w]) return false;
}
return true;

Same cost as BFS: O(n + m). Uses: matching students to projects, two-shift scheduling, conflict-free grouping.

Traversal (BFS / DFS)

Topological Sorting: DAGs Only

  • Orders the vertices of a digraph so that for every edge u → v, u comes before v.
  • Possible only in directed acyclic graphs (DAGs).
  • The result need not be unique: several vertices of in-degree 0 may be available at the same time.
  • Uses: course prerequisites, task dependencies, build and workflow planning.
A graph with a cycle has no topological order: every task on the cycle waits for another task on it to be done first.
Kahn(G):
  q = vertices with inDegree 0
  result = []
  while q is not empty:
    u = remove from q
    append u to result
    for each edge (u, v) leaving u:
      inDegree[v]--
      if inDegree[v] == 0:
        add v to q
  if result.size < n:
    there is a cycle
  return result
TimeO(n + m)
AlternativeReverse the DFS finishing order; finding a back edge means there is a cycle.

Traversal (BFS / DFS) · Step through the code

Code Trace: Topological Sorting (Kahn)

List<Integer> kahn(List<List<Integer>> adj) {
  int n = adj.size();
  int[] indeg = new int[n];
  for (List<Integer> l : adj)
    for (int v : l) indeg[v]++;
  Queue<Integer> q = new ArrayDeque<>();
  for (int v = 0; v < n; v++)
    if (indeg[v] == 0) q.add(v);
  List<Integer> result = new ArrayList<>();
  while (!q.isEmpty()) {
    int u = q.poll();
    result.add(u);
    for (int v : adj.get(u))
      if (--indeg[v] == 0) q.add(v);
  }
  return result;  // size < n → cycle
}
1 2 0 3 4 5 indeg[] (in-degree) 0 1 2 3 4 5 indeg 0 0 0 0 0 0 q (empty) result [ ]

A course-prerequisite example: 6 vertices, 7 directed edges (u → v: u before v). indeg[] starts at all zeros.

1 2 0 3 4 5 indeg[] (in-degree) 0 1 2 3 4 5 indeg 0 0 0 1 0 0 q (empty) result [ ]

Edges out of 0 (0→3): each target's in-degree goes up by one.

1 2 0 3 4 5 indeg[] (in-degree) 0 1 2 3 4 5 indeg 0 0 0 2 1 0 q (empty) result [ ]

Edges out of 1 (1→3, 1→4): each target's in-degree goes up by one.

1 2 0 3 4 5 indeg[] (in-degree) 0 1 2 3 4 5 indeg 1 0 0 2 2 0 q (empty) result [ ]

Edges out of 2 (2→0, 2→4): each target's in-degree goes up by one.

1 2 0 3 4 5 indeg[] (in-degree) 0 1 2 3 4 5 indeg 1 0 0 2 2 1 q (empty) result [ ]

Edges out of 3 (3→5): each target's in-degree goes up by one.

1 2 0 3 4 5 indeg[] (in-degree) 0 1 2 3 4 5 indeg 1 0 0 2 2 2 q (empty) result [ ]

Edges out of 4 (4→5): each target's in-degree goes up by one.

1 2 0 3 4 5 indeg[] (in-degree) 0 1 2 3 4 5 indeg 1 0 0 2 2 2 q 1 2 result [ ]

The vertices of in-degree 0 (1, 2) depend on nothing → enqueue them. (5's list is empty, so its counting round changed nothing.)

1 2 0 3 4 5 indeg[] (in-degree) 0 1 2 3 4 5 indeg 1 0 0 2 2 2 q 2 result 1

1 leaves the queue and joins the result: all its prerequisites are already there.

1 2 0 3 4 5 indeg[] (in-degree) 0 1 2 3 4 5 indeg 1 0 0 1 2 2 q 2 result 1

Count 1→3 as deleted: indeg[3] = 1; 3 still waits for another prerequisite.

1 2 0 3 4 5 indeg[] (in-degree) 0 1 2 3 4 5 indeg 1 0 0 1 1 2 q 2 result 1

Count 1→4 as deleted: indeg[4] = 1; 4 still waits for another prerequisite.

1 2 0 3 4 5 indeg[] (in-degree) 0 1 2 3 4 5 indeg 1 0 0 1 1 2 q (empty) result 1 2

2 leaves the queue and joins the result: all its prerequisites are already there.

Traversal (BFS / DFS) · Step through the code

Code Trace: Topological Sorting (Kahn, continued)

List<Integer> kahn(List<List<Integer>> adj) {
  int n = adj.size();
  int[] indeg = new int[n];
  for (List<Integer> l : adj)
    for (int v : l) indeg[v]++;
  Queue<Integer> q = new ArrayDeque<>();
  for (int v = 0; v < n; v++)
    if (indeg[v] == 0) q.add(v);
  List<Integer> result = new ArrayList<>();
  while (!q.isEmpty()) {
    int u = q.poll();
    result.add(u);
    for (int v : adj.get(u))
      if (--indeg[v] == 0) q.add(v);
  }
  return result;  // size < n → cycle
}
1 2 0 3 4 5 indeg[] (in-degree) 0 1 2 3 4 5 indeg 0 0 0 1 1 2 q 0 result 1 2

Count 2→0 as deleted: indeg[0] = 0 → 0 is ready and joins the queue.

1 2 0 3 4 5 indeg[] (in-degree) 0 1 2 3 4 5 indeg 0 0 0 1 0 2 q 0 4 result 1 2

Count 2→4 as deleted: indeg[4] = 0 → 4 is ready and joins the queue.

1 2 0 3 4 5 indeg[] (in-degree) 0 1 2 3 4 5 indeg 0 0 0 1 0 2 q 4 result 1 2 0

0 leaves the queue and joins the result: all its prerequisites are already there.

1 2 0 3 4 5 indeg[] (in-degree) 0 1 2 3 4 5 indeg 0 0 0 0 0 2 q 4 3 result 1 2 0

Count 0→3 as deleted: indeg[3] = 0 → 3 is ready and joins the queue.

1 2 0 3 4 5 indeg[] (in-degree) 0 1 2 3 4 5 indeg 0 0 0 0 0 2 q 3 result 1 2 0 4

4 leaves the queue and joins the result: all its prerequisites are already there.

1 2 0 3 4 5 indeg[] (in-degree) 0 1 2 3 4 5 indeg 0 0 0 0 0 1 q 3 result 1 2 0 4

Count 4→5 as deleted: indeg[5] = 1; 5 still waits for another prerequisite.

1 2 0 3 4 5 indeg[] (in-degree) 0 1 2 3 4 5 indeg 0 0 0 0 0 1 q (empty) result 1 2 0 4 3

3 leaves the queue and joins the result: all its prerequisites are already there.

1 2 0 3 4 5 indeg[] (in-degree) 0 1 2 3 4 5 indeg 0 0 0 0 0 0 q 5 result 1 2 0 4 3

Count 3→5 as deleted: indeg[5] = 0 → 5 is ready and joins the queue.

1 2 0 3 4 5 indeg[] (in-degree) 0 1 2 3 4 5 indeg 0 0 0 0 0 0 q (empty) result 1 2 0 4 3 5

5 leaves the queue and joins the result: all its prerequisites are already there.

1 2 0 3 4 5 indeg[] (in-degree) 0 1 2 3 4 5 indeg 0 0 0 0 0 0 q (empty) result 1 2 0 4 3 5

The queue is empty; the result is 1 2 0 4 3 5, and its size 6 = n → no cycle. Each edge caused one decrement: O(n + m).

Traversal (BFS / DFS) · Step through the code

Code Trace: Cycles in a Digraph (Three-Color DFS)

0 1 2 3 4 v color 1 0 0 1 0 2 0 3 0 4 0 white · 1 gray · 2 black

color[0] = 1 (gray): 0 is on the current DFS path.

0 1 2 3 4 v color 1 0 0 1 0 2 0 3 0 4 0 white · 1 gray · 2 black

Edge 0 → 1: 1 is white → recursive call.

0 1 2 3 4 v color 1 0 1 1 0 2 0 3 0 4 0 white · 1 gray · 2 black

color[1] = 1 (gray): 1 is on the current DFS path.

0 1 2 3 4 v color 1 0 1 1 0 2 0 3 0 4 0 white · 1 gray · 2 black

Edge 1 → 2: 2 is white → recursive call.

0 1 2 3 4 v color 1 0 1 1 1 2 0 3 0 4 0 white · 1 gray · 2 black

color[2] = 1 (gray): 2 is on the current DFS path.

0 1 2 3 4 v color 1 0 1 1 2 2 0 3 0 4 0 white · 1 gray · 2 black

2 has no outgoing edges: color[2] = 2 (black); return false and pop the frame.

0 1 2 3 4 v color 1 0 2 1 2 2 0 3 0 4 0 white · 1 gray · 2 black

1's neighbors are all done: color[1] = 2 (black); return false and pop the frame.

0 1 2 3 4 v color 1 0 2 1 2 2 0 3 0 4 0 white · 1 gray · 2 black

Edge 0 → 3: 3 is white → recursive call.

0 1 2 3 4 v color 1 0 2 1 2 2 1 3 0 4 0 white · 1 gray · 2 black

color[3] = 1 (gray): 3 is on the current DFS path.

0 1 2 3 4 v color 1 0 2 1 2 2 1 3 0 4 0 white · 1 gray · 2 black

Edge 3 → 2: 2 is black (finished). Not a cycle: there is no path from 2 back to 3. Only a gray vertex signals a cycle.

0 1 2 3 4 v color 1 0 2 1 2 2 1 3 0 4 0 white · 1 gray · 2 black

Edge 3 → 4: 4 is white → recursive call.

0 1 2 3 4 v color 1 0 2 1 2 2 1 3 1 4 0 white · 1 gray · 2 black

color[4] = 1 (gray): 4 is on the current DFS path.

0 1 2 3 4 v color 1 0 2 1 2 2 1 3 1 4 0 white · 1 gray · 2 black back edge → CYCLE

Edge 4 → 0: 0 is gray, so it is on the path! This is a back edge → cycle 0 → 3 → 4 → 0 found.

0 1 2 3 4 v color 1 0 2 1 2 2 1 3 1 4 0 white · 1 gray · 2 black back edge → CYCLE

The inner call returned true → 3 returns true too.

0 1 2 3 4 v color 1 0 2 1 2 2 1 3 1 4 0 white · 1 gray · 2 black back edge → CYCLE

The inner call returned true → 0 returns true too.

// color: 0 white, 1 gray (on path), 2 black
boolean hasCycle(int v, int[] color) {
  color[v] = 1;
  for (int u : adj.get(v)) {
    if (color[u] == 1) return true;  // back edge
    if (color[u] == 0 && hasCycle(u, color))
      return true;
  }
  color[v] = 2;
  return false;
}

Shortest Paths

The Unweighted Shortest-Path Problem

  • Find shortest paths from a source vertex to every other vertex (single-source shortest paths).
  • Every edge is assumed to have the same weight (1): path length = number of edges.
  • Solution: BFS. A vertex on level k is exactly k edges away.
1 2 5 3 8 4 6 7 0 1 2 1 2 1 2 3

Source 1; the number next to each vertex is its distance from 1

There are two shortest paths to 7: 1 → 4 → 6 → 7 and 1 → 3 → 8 → 7 (3 edges each). BFS finds one of them; either way, the length is 3.

Shortest Paths

The Unweighted Shortest-Path Algorithm

Pseudocode
dist[every v] = ∞
dist[s] = 0
queue.add(s)
while queue is not empty:
  w = queue.remove()
  for each neighbor u of w:
    if dist[u] == ∞:
      dist[u] = dist[w] + 1
      queue.add(u)
return dist

"dist = ∞" also means "not visited", so no separate array is needed. In Java, graph maps each vertex to its neighbor list (Map<Integer, List<Integer>>), because the vertices are numbered from 1.

Java · O(n + m)
Map<Integer, Integer> shortestPath(int s) {
  final int INF = Integer.MAX_VALUE;  // ∞
  Map<Integer, Integer> dist =
      new HashMap<>();
  for (int v : graph.keySet())
    dist.put(v, INF);
  dist.put(s, 0);
  Queue<Integer> q = new ArrayDeque<>();
  q.offer(s);
  while (!q.isEmpty()) {
    int w = q.poll();
    for (int u : graph.get(w))
      if (dist.get(u) == INF) {
        dist.put(u, dist.get(w) + 1);
        q.offer(u);
      }
  }
  return dist;
}

Shortest Paths · Step through the code

Code Trace: shortestPath(1)

Map<Integer, Integer> shortestPath(int s) {
  final int INF = Integer.MAX_VALUE;  // ∞
  Map<Integer, Integer> dist =
      new HashMap<>();
  for (int v : graph.keySet())
    dist.put(v, INF);
  dist.put(s, 0);
  Queue<Integer> q = new ArrayDeque<>();
  q.offer(s);
  while (!q.isEmpty()) {
    int w = q.poll();
    for (int u : graph.get(w))
      if (dist.get(u) == INF) {
        dist.put(u, dist.get(w) + 1);
        q.offer(u);
      }
  }
  return dist;
}
1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ q (empty)

Source s = 1. All distances are INF (∞): "not reached yet" = "not visited".

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 ∞ ∞ ∞ ∞ ∞ ∞ ∞ q 1

dist[1] = 0, and 1 enters the queue.

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 ∞ ∞ ∞ ∞ ∞ ∞ ∞ q (empty)

Dequeue 1; dist[1] = 0. Examine its neighbors 2, 3, 4.

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 1 ∞ ∞ ∞ ∞ ∞ ∞ q 2

dist[2] = INF → dist[2] = 0 + 1 = 1; enqueue 2.

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 1 1 ∞ ∞ ∞ ∞ ∞ q 2 3

dist[3] = INF → dist[3] = 0 + 1 = 1; enqueue 3.

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 1 1 1 ∞ ∞ ∞ ∞ q 2 3 4

dist[4] = INF → dist[4] = 0 + 1 = 1; enqueue 4.

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 1 1 1 ∞ ∞ ∞ ∞ q 3 4

Dequeue 2; dist[2] = 1. Examine its neighbors 1, 5.

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 1 1 1 ∞ ∞ ∞ ∞ q 3 4

1: distance already known (0) → unchanged. In BFS, the first distance found is the shortest.

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 1 1 1 2 ∞ ∞ ∞ q 3 4 5

dist[5] = INF → dist[5] = 1 + 1 = 2; enqueue 5.

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 1 1 1 2 ∞ ∞ ∞ q 4 5

Dequeue 3; dist[3] = 1. Examine its neighbors 1, 8.

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 1 1 1 2 ∞ ∞ ∞ q 4 5

1: distance already known (0) → unchanged. In BFS, the first distance found is the shortest.

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 1 1 1 2 ∞ ∞ 2 q 4 5 8

dist[8] = INF → dist[8] = 1 + 1 = 2; enqueue 8.

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 1 1 1 2 ∞ ∞ 2 q 5 8

Dequeue 4; dist[4] = 1. Examine its neighbors 1, 6.

Shortest Paths · Step through the code

Code Trace: shortestPath(1) (continued)

Map<Integer, Integer> shortestPath(int s) {
  final int INF = Integer.MAX_VALUE;  // ∞
  Map<Integer, Integer> dist =
      new HashMap<>();
  for (int v : graph.keySet())
    dist.put(v, INF);
  dist.put(s, 0);
  Queue<Integer> q = new ArrayDeque<>();
  q.offer(s);
  while (!q.isEmpty()) {
    int w = q.poll();
    for (int u : graph.get(w))
      if (dist.get(u) == INF) {
        dist.put(u, dist.get(w) + 1);
        q.offer(u);
      }
  }
  return dist;
}
1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 1 1 1 2 ∞ ∞ 2 q 5 8

1: distance already known (0) → unchanged. In BFS, the first distance found is the shortest.

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 1 1 1 2 2 ∞ 2 q 5 8 6

dist[6] = INF → dist[6] = 1 + 1 = 2; enqueue 6.

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 1 1 1 2 2 ∞ 2 q 8 6

Dequeue 5; dist[5] = 2. Examine its neighbors 2, 8.

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 1 1 1 2 2 ∞ 2 q 8 6

2, 8: distance already known (1, 2) → unchanged. In BFS, the first distance found is the shortest.

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 1 1 1 2 2 ∞ 2 q 6

Dequeue 8; dist[8] = 2. Examine its neighbors 3, 5, 7.

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 1 1 1 2 2 ∞ 2 q 6

3, 5: distance already known (1, 2) → unchanged. In BFS, the first distance found is the shortest.

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 1 1 1 2 2 3 2 q 6 7

dist[7] = INF → dist[7] = 2 + 1 = 3; enqueue 7.

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 1 1 1 2 2 3 2 q 7

Dequeue 6; dist[6] = 2. Examine its neighbors 4, 7.

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 1 1 1 2 2 3 2 q 7

4, 7: distance already known (1, 3) → unchanged. In BFS, the first distance found is the shortest.

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 1 1 1 2 2 3 2 q (empty)

Dequeue 7; dist[7] = 3. Examine its neighbors 6, 8.

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 1 1 1 2 2 3 2 q (empty)

6, 8: distance already known (2, 2) → unchanged. In BFS, the first distance found is the shortest.

1 2 5 3 8 4 6 7 1 2 3 4 5 6 7 8 dist 0 1 1 1 2 2 3 2 q (empty)

The queue is empty → return dist. The distance to 7 is 3 (1 → 3 → 8 → 7 was found; 1 → 4 → 6 → 7 also has 3 edges). O(n + m).

Shortest Paths

The Weighted Shortest-Path Problem

  • Find shortest paths from a source vertex to every other vertex.
  • Each edge may have its own weight: a distance, time, cost…
  • Path length = sum of the edge weights.
4 2 5 10 3 4 11 A B D C E F

Shortest path from A to F: A → C → E → D → F = 2 + 3 + 4 + 11 = 20

BFS is not enough hereThe path with the fewest edges is A → B → D → F (3 edges), but its length is 4 + 10 + 11 = 25.
Solution: Dijkstra's algorithmEdsger W. Dijkstra, 1956. Weights must not be negative.

Shortest Paths

Dijkstra's Algorithm: Idea and Pseudocode

  • Pick the unsettled vertex with the smallest distance.
  • Update its neighbors' distances by relaxation.
  • Use a priority queue for the selection and prev[] to rebuild the path.
Dijkstra(G, s):
  for each vertex v:
    dist[v] = ∞
    prev[v] = undefined
  dist[s] = 0
  Q = all vertices  // priority queue
  while Q is not empty:
    u = Q.removeMin()   // smallest dist[u]
    for each neighbor v of u in Q:
      alt = dist[u] + weight(u, v)
      if alt < dist[v]:
        dist[v] = alt
        prev[v] = u
        // decrease v's priority in Q

Shortest Paths

Dijkstra's Algorithm in Java

long[] dijkstra(List<List<int[]>> adj, int s, int[] prev) { // weights ≥ 0
    long[] d = new long[adj.size()];
    Arrays.fill(d, Long.MAX_VALUE);  d[s] = 0;  Arrays.fill(prev, -1);
    PriorityQueue<long[]> pq =                     // {vertex, dist}
        new PriorityQueue<>((a, b) -> Long.compare(a[1], b[1]));
    pq.add(new long[]{s, 0});
    while (!pq.isEmpty()) {
        long[] e = pq.poll();  int u = (int) e[0];
        if (e[1] > d[u]) continue;                   // stale entry: skip
        for (int[] k : adj.get(u)) {
            int v = k[0];  long alt = d[u] + k[1];
            if (alt < d[v]) { d[v] = alt; prev[v] = u; pq.add(new long[]{v, alt}); }
        }
    }
    return d;
}
Priority = distance (not the vertex number). Java's PriorityQueue has no "decrease key": we add a new entry, and the old one is skipped as stale when it comes out. prev[v] = u records the shortest-path tree; a path is traced back from the target.

Shortest Paths · Step through the code

Code Trace: dijkstra(adj, A)

// d[] = ∞, d[s] = 0, prev[] = -1
pq.add(new long[]{s, 0});
while (!pq.isEmpty()) {
  long[] e = pq.poll();
  int u = (int) e[0];
  if (e[1] > d[u]) continue;  // stale
  for (int[] k : adj.get(u)) {
    int v = k[0];
    long alt = d[u] + k[1];
    if (alt < d[v]) {
      d[v] = alt;
      prev[v] = u;
      pq.add(new long[]{v, alt});
    }
  }
}
3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ prev – – – – – – – – – pq (shown sorted by distance; faded = stale) A,0

Source A: d[A] = 0, all others ∞. Add {A, 0} to the queue. Priority = distance.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ prev – – – – – – – – – pq (shown sorted by distance; faded = stale) (empty)

Poll the smallest entry {A, 0}; 0 = d[A] (current) → A is settled. Relax its neighbors B, F, I.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 ∞ ∞ ∞ ∞ ∞ ∞ ∞ prev – A – – – – – – – pq (shown sorted by distance; faded = stale) B,4

v = B: alt = 0 + 4 = 4 < ∞ → d[B] = 4, prev[B] = A, {B, 4} enters the queue.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 ∞ ∞ ∞ 7 ∞ ∞ ∞ prev – A – – – A – – – pq (shown sorted by distance; faded = stale) B,4 F,7

v = F: alt = 0 + 7 = 7 < ∞ → d[F] = 7, prev[F] = A, {F, 7} enters the queue.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 ∞ ∞ ∞ 7 ∞ ∞ 3 prev – A – – – A – – A pq (shown sorted by distance; faded = stale) I,3 B,4 F,7

v = I: alt = 0 + 3 = 3 < ∞ → d[I] = 3, prev[I] = A, {I, 3} enters the queue.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 ∞ ∞ ∞ 7 ∞ ∞ 3 prev – A – – – A – – A pq (shown sorted by distance; faded = stale) B,4 F,7

Poll the smallest entry {I, 3}; 3 = d[I] (current) → I is settled. Relax its neighbor A.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 ∞ ∞ ∞ 7 ∞ ∞ 3 prev – A – – – A – – A pq (shown sorted by distance; faded = stale) B,4 F,7

v = A: alt = 6 ≥ d[A] = 0 → no update.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 ∞ ∞ ∞ 7 ∞ ∞ 3 prev – A – – – A – – A pq (shown sorted by distance; faded = stale) F,7

Poll the smallest entry {B, 4}; 4 = d[B] (current) → B is settled. Relax its neighbors A, C, E.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 ∞ ∞ ∞ 7 ∞ ∞ 3 prev – A – – – A – – A pq (shown sorted by distance; faded = stale) F,7

v = A: alt = 8 ≥ d[A] = 0 → no update.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 5 ∞ ∞ 7 ∞ ∞ 3 prev – A B – – A – – A pq (shown sorted by distance; faded = stale) C,5 F,7

v = C: alt = 4 + 1 = 5 < ∞ → d[C] = 5, prev[C] = B, {C, 5} enters the queue.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 5 ∞ 6 7 ∞ ∞ 3 prev – A B – B A – – A pq (shown sorted by distance; faded = stale) C,5 E,6 F,7

v = E: alt = 4 + 2 = 6 < ∞ → d[E] = 6, prev[E] = B, {E, 6} enters the queue.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 5 ∞ 6 7 ∞ ∞ 3 prev – A B – B A – – A pq (shown sorted by distance; faded = stale) E,6 F,7

Poll the smallest entry {C, 5}; 5 = d[C] (current) → C is settled. Relax its neighbors B, D, E.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 5 ∞ 6 7 ∞ ∞ 3 prev – A B – B A – – A pq (shown sorted by distance; faded = stale) E,6 F,7

v = B: alt = 6 ≥ d[B] = 4 → no update.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 5 12 6 7 ∞ ∞ 3 prev – A B C B A – – A pq (shown sorted by distance; faded = stale) E,6 F,7 D,12

v = D: alt = 5 + 7 = 12 < ∞ → d[D] = 12, prev[D] = C, {D, 12} enters the queue.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 5 12 6 7 ∞ ∞ 3 prev – A B C B A – – A pq (shown sorted by distance; faded = stale) E,6 F,7 D,12

v = E: alt = 8 ≥ d[E] = 6 → no update.

Shortest Paths · Step through the code

Code Trace: dijkstra(adj, A) (continued)

// d[] = ∞, d[s] = 0, prev[] = -1
pq.add(new long[]{s, 0});
while (!pq.isEmpty()) {
  long[] e = pq.poll();
  int u = (int) e[0];
  if (e[1] > d[u]) continue;  // stale
  for (int[] k : adj.get(u)) {
    int v = k[0];
    long alt = d[u] + k[1];
    if (alt < d[v]) {
      d[v] = alt;
      prev[v] = u;
      pq.add(new long[]{v, alt});
    }
  }
}
3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 5 12 6 7 ∞ ∞ 3 prev – A B C B A – – A pq (shown sorted by distance; faded = stale) F,7 D,12

Poll the smallest entry {E, 6}; 6 = d[E] (current) → E is settled. Relax its neighbors B, C, G.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 5 12 6 7 ∞ ∞ 3 prev – A B C B A – – A pq (shown sorted by distance; faded = stale) F,7 D,12

v = B: alt = 8 ≥ d[B] = 4; v = C: alt = 9 ≥ d[C] = 5 → no update.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 5 12 6 7 8 ∞ 3 prev – A B C B A E – A pq (shown sorted by distance; faded = stale) F,7 G,8 D,12

v = G: alt = 6 + 2 = 8 < ∞ → d[G] = 8, prev[G] = E, {G, 8} enters the queue.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 5 12 6 7 8 ∞ 3 prev – A B C B A E – A pq (shown sorted by distance; faded = stale) G,8 D,12

Poll the smallest entry {F, 7}; 7 = d[F] (current) → F is settled. Relax its neighbors A, G.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 5 12 6 7 8 ∞ 3 prev – A B C B A E – A pq (shown sorted by distance; faded = stale) G,8 D,12

v = A: alt = 14 ≥ d[A] = 0; v = G: alt = 9 ≥ d[G] = 8 → no update.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 5 12 6 7 8 ∞ 3 prev – A B C B A E – A pq (shown sorted by distance; faded = stale) D,12

Poll the smallest entry {G, 8}; 8 = d[G] (current) → G is settled. Relax its neighbors D, E, F.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 5 11 6 7 8 ∞ 3 prev – A B G B A E – A pq (shown sorted by distance; faded = stale) D,11 D,12

v = D: alt = 8 + 3 = 11 < 12 → d[D] = 11, prev[D] = G, {D, 11} enters the queue. The old entry {D, 12} stays in the queue as stale.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 5 11 6 7 8 ∞ 3 prev – A B G B A E – A pq (shown sorted by distance; faded = stale) D,11 D,12

v = E: alt = 10 ≥ d[E] = 6; v = F: alt = 10 ≥ d[F] = 7 → no update.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 5 11 6 7 8 ∞ 3 prev – A B G B A E – A pq (shown sorted by distance; faded = stale) D,12

Poll the smallest entry {D, 11}; 11 = d[D] (current) → D is settled. Relax its neighbors C, G, H.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 5 11 6 7 8 ∞ 3 prev – A B G B A E – A pq (shown sorted by distance; faded = stale) D,12

v = C: alt = 18 ≥ d[C] = 5; v = G: alt = 14 ≥ d[G] = 8 → no update.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 5 11 6 7 8 16 3 prev – A B G B A E D A pq (shown sorted by distance; faded = stale) D,12 H,16

v = H: alt = 11 + 5 = 16 < ∞ → d[H] = 16, prev[H] = D, {H, 16} enters the queue.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 5 11 6 7 8 16 3 prev – A B G B A E D A pq (shown sorted by distance; faded = stale) H,16

{D, 12} comes out, but d[D] = 11 < 12: a stale entry (a better path was found later) → continue skips it.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 5 11 6 7 8 16 3 prev – A B G B A E D A pq (shown sorted by distance; faded = stale) (empty)

Poll the smallest entry {H, 16}; 16 = d[H] (current) → H is settled. Relax its neighbor D.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 5 11 6 7 8 16 3 prev – A B G B A E D A pq (shown sorted by distance; faded = stale) (empty)

v = D: alt = 21 ≥ d[D] = 11 → no update.

3 4 7 1 2 3 7 2 2 3 5 I A B C D H G F E A B C D E F G H I d[] 0 4 5 11 6 7 8 16 3 prev – A B G B A E D A pq (shown sorted by distance; faded = stale) (empty)

The queue is empty → done. Trace prev[] back: H ← D ← G ← E ← B ← A. The shortest path is A → B → E → G → D → H = 16.

Shortest Paths · Step through the code

Code Trace: Printing the Shortest Path with prev[]

4 2 5 10 3 4 11 A B D C E F v prev – A A B A C E D C E D F y addFirst → from target back to source, insert at front

The prev[] array Dijkstra leaves behind: for each vertex, the vertex just before it on its shortest path. List p starts empty.

4 2 5 10 3 4 11 A B D C E F v prev – A A B A C E D C E D F y addFirst → from target back to source, insert at front

Start at the target: v = F.

4 2 5 10 3 4 11 A B D C E F v prev – A A B A C E D C E D F y F addFirst → from target back to source, insert at front

F goes to the front of the list (vertices added later come earlier on the path).

4 2 5 10 3 4 11 A B D C E F v prev – A A B A C E D C E D F y F addFirst → from target back to source, insert at front

v = prev[…] = D: one step back.

4 2 5 10 3 4 11 A B D C E F v prev – A A B A C E D C E D F y D F addFirst → from target back to source, insert at front

D goes to the front of the list (vertices added later come earlier on the path).

4 2 5 10 3 4 11 A B D C E F v prev – A A B A C E D C E D F y D F addFirst → from target back to source, insert at front

v = prev[…] = E: one step back.

4 2 5 10 3 4 11 A B D C E F v prev – A A B A C E D C E D F y E D F addFirst → from target back to source, insert at front

E goes to the front of the list (vertices added later come earlier on the path).

4 2 5 10 3 4 11 A B D C E F v prev – A A B A C E D C E D F y E D F addFirst → from target back to source, insert at front

v = prev[…] = C: one step back.

4 2 5 10 3 4 11 A B D C E F v prev – A A B A C E D C E D F y C E D F addFirst → from target back to source, insert at front

C goes to the front of the list (vertices added later come earlier on the path).

4 2 5 10 3 4 11 A B D C E F v prev – A A B A C E D C E D F y C E D F addFirst → from target back to source, insert at front

v = prev[…] = A: one step back.

4 2 5 10 3 4 11 A B D C E F v prev – A A B A C E D C E D F y A C E D F addFirst → from target back to source, insert at front

A goes to the front of the list (vertices added later come earlier on the path).

4 2 5 10 3 4 11 A B D C E F v prev – A A B A C E D C E D F y A C E D F addFirst → from target back to source, insert at front

prev[A] = −1: we have reached the source, so the loop ends. 5 rounds = vertices on the path → O(path length).

4 2 5 10 3 4 11 A B D C E F v prev – A A B A C E D C E D F y A C E D F addFirst → from target back to source, insert at front

Is the first element the source A? Yes → t is reachable. (For an unreachable t, the chain never reaches A and an empty list is returned.)

4 2 5 10 3 4 11 A B D C E F v prev – A A B A C E D C E D F y A C E D F addFirst → from target back to source, insert at front

Result: A → C → E → D → F, length d[F] = 2 + 3 + 4 + 11 = 20.

List<Integer> path(int[] prev, int s, int t) {
  LinkedList<Integer> p = new LinkedList<>();
  for (int v = t; v != -1; v = prev[v])
    p.addFirst(v);
  if (p.getFirst() != s) return List.of();
  return p;                 // s ... t
}

Shortest Paths

Analyzing Dijkstra's Algorithm

ImplementationComplexityWhy?
Priority queue (binary heap)O((n + m) log n)n remove-mins and at most m updates/inserts, each O(log n)
No queue (array scan)O(n² + m) = O(n²)Each step scans n vertices for the smallest distance; neighbor checks total m
Which one when?For a sparse graph (m ≈ n), the heap gives O(n log n). For a very dense graph (m ≈ n²), the O(n²) array scan can be better.
Negative weightsA settled distance could later shrink through a negative edge → the pseudocode version of Dijkstra gives wrong answers. The Java version with stale entries can reprocess a vertex and still find the right answer, but loses its time bound and never finishes on a negative cycle. Use Bellman-Ford instead.
Why does it work? With weights ≥ 0, the vertex with the smallest distance in the queue cannot be reached more cheaply through some other vertex: every other route is already at least that long.

Minimum Spanning Trees

Minimum Spanning Trees

  • Spanning tree: a subgraph of a connected, weighted graph that contains every vertex and no cycle; it has n − 1 edges for n vertices.
  • Minimum spanning tree (MST): a spanning tree with the smallest total edge weight.
  • Application: connecting cities with the least cable or pipe.
  • Kruskal's algorithm answers "Does this edge form a cycle?" with Union-Find (disjoint sets): find(x) returns the representative of x's set; union(u, v) merges two different sets and returns true, or returns false if they are already the same set.
0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J

Minimum Spanning Trees

Kruskal's Algorithm

Kruskal(G):
  T = ∅          // chosen edges
  for each vertex v: makeSet(v)
  sort the edges by weight
  for each (u, v), lightest first:
    if find(u) != find(v): // no cycle
      T.add((u, v))
      union(u, v)
    if |T| == n − 1: break
  return T  // MST (if disconnected: forest)
  • Greedy: always take the lightest edge that does not form a cycle.
  • If u and v are already in the same set, the edge closes a cycle → skip it.
  • Start with n disjoint sets; each accepted edge merges two of them.
  • If the graph is disconnected, n−1 edges are never reached; the algorithm returns a minimum spanning forest, one tree per connected component.
SortingO(m log m)
2m find + (n−1) unionO(m · α(n))
TotalO(m log n)

Minimum Spanning Trees

Kruskal's Algorithm Step by Step

I–J = 0 A–E = 1 C–I = 1 E–F = 1 G–H = 1 B–D = 2 C–J = 2 D–E = 2 D–H = 2 A–D = 4 B–C = 4 C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Selected edge 0 / 9 Total weight 0

The edges are sorted by weight (left). Initially 10 vertices = 10 separate sets; T is empty.

I–J = 0 ✓ A–E = 1 C–I = 1 E–F = 1 G–H = 1 B–D = 2 C–J = 2 D–E = 2 D–H = 2 A–D = 4 B–C = 4 C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Selected edge 1 / 9 Total weight 0

I–J (0): find(I) ≠ find(J) → accept; the sets merge.

I–J = 0 ✓ A–E = 1 ✓ C–I = 1 E–F = 1 G–H = 1 B–D = 2 C–J = 2 D–E = 2 D–H = 2 A–D = 4 B–C = 4 C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Selected edge 2 / 9 Total weight 1

A–E (1): find(A) ≠ find(E) → accept; the sets merge.

I–J = 0 ✓ A–E = 1 ✓ C–I = 1 ✓ E–F = 1 G–H = 1 B–D = 2 C–J = 2 D–E = 2 D–H = 2 A–D = 4 B–C = 4 C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Selected edge 3 / 9 Total weight 2

C–I (1): find(C) ≠ find(I) → accept; the sets merge.

I–J = 0 ✓ A–E = 1 ✓ C–I = 1 ✓ E–F = 1 ✓ G–H = 1 B–D = 2 C–J = 2 D–E = 2 D–H = 2 A–D = 4 B–C = 4 C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Selected edge 4 / 9 Total weight 3

E–F (1): find(E) ≠ find(F) → accept; the sets merge.

I–J = 0 ✓ A–E = 1 ✓ C–I = 1 ✓ E–F = 1 ✓ G–H = 1 ✓ B–D = 2 C–J = 2 D–E = 2 D–H = 2 A–D = 4 B–C = 4 C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Selected edge 5 / 9 Total weight 4

G–H (1): find(G) ≠ find(H) → accept; the sets merge.

I–J = 0 ✓ A–E = 1 ✓ C–I = 1 ✓ E–F = 1 ✓ G–H = 1 ✓ B–D = 2 ✓ C–J = 2 D–E = 2 D–H = 2 A–D = 4 B–C = 4 C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Selected edge 6 / 9 Total weight 6

B–D (2): find(B) ≠ find(D) → accept; the sets merge.

I–J = 0 ✓ A–E = 1 ✓ C–I = 1 ✓ E–F = 1 ✓ G–H = 1 ✓ B–D = 2 ✓ C–J = 2 ✗ D–E = 2 D–H = 2 A–D = 4 B–C = 4 C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Selected edge 6 / 9 Total weight 6

C–J (2): C and J are already in the same set → it would form a cycle, so it is rejected.

I–J = 0 ✓ A–E = 1 ✓ C–I = 1 ✓ E–F = 1 ✓ G–H = 1 ✓ B–D = 2 ✓ C–J = 2 ✗ D–E = 2 ✓ D–H = 2 A–D = 4 B–C = 4 C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Selected edge 7 / 9 Total weight 8

D–E (2): find(D) ≠ find(E) → accept; the sets merge.

I–J = 0 ✓ A–E = 1 ✓ C–I = 1 ✓ E–F = 1 ✓ G–H = 1 ✓ B–D = 2 ✓ C–J = 2 ✗ D–E = 2 ✓ D–H = 2 ✓ A–D = 4 B–C = 4 C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Selected edge 8 / 9 Total weight 10

D–H (2): find(D) ≠ find(H) → accept; the sets merge.

I–J = 0 ✓ A–E = 1 ✓ C–I = 1 ✓ E–F = 1 ✓ G–H = 1 ✓ B–D = 2 ✓ C–J = 2 ✗ D–E = 2 ✓ D–H = 2 ✓ A–D = 4 ✗ B–C = 4 C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Selected edge 8 / 9 Total weight 10

A–D (4): A and D are already in the same set → it would form a cycle, so it is rejected.

I–J = 0 ✓ A–E = 1 ✓ C–I = 1 ✓ E–F = 1 ✓ G–H = 1 ✓ B–D = 2 ✓ C–J = 2 ✗ D–E = 2 ✓ D–H = 2 ✓ A–D = 4 ✗ B–C = 4 ✓ C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Selected edge 9 / 9 Total weight 14

B–C (4): find(B) ≠ find(C) → accept; the sets merge. 9 = n − 1 edges chosen: done, total weight 14.

I–J = 0 ✓ A–E = 1 ✓ C–I = 1 ✓ E–F = 1 ✓ G–H = 1 ✓ B–D = 2 ✓ C–J = 2 ✗ D–E = 2 ✓ D–H = 2 ✓ A–D = 4 ✗ B–C = 4 ✓ C–H = 4 G–I = 4 A–B = 5 D–F = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Selected edge 9 / 9 Total weight 14

Minimum spanning tree: A–E, E–F, D–E, B–D, D–H, G–H, B–C, C–I, I–J. The remaining edges need not be examined.

Minimum Spanning Trees · Step through the code

Code Trace: kruskal() + Union-Find

List<int[]> kruskal(int n, List<int[]> E) {
  E.sort((a, b) ->          // {u, v, w}
      Integer.compare(a[2], b[2]));
  UnionFind uf = new UnionFind(n);
  List<int[]> T = new ArrayList<>();
  for (int[] e : E) {
    if (uf.union(e[0], e[1]))  // no cycle
      T.add(e);
    if (T.size() == n - 1) break;
  }
  return T;
}
A B C D E F G H I J edges (sorted) I–J 0 A–E 1 C–I 1 E–F 1 G–H 1 B–D 2 C–J 2 D–E 2 D–H 2 A–D 4 B–C 4 C–H 4 G–I 4 A–B 5 D–F 5 H–I 6 F–G 7 D–G 11 uf forest (arrow → parent) A B C D E F G H I J |T|=0, weight=0

Sort the edges by weight (A=0 … J=9; sort is stable, so equal weights keep their input order). O(m log m).

A B C D E F G H I J edges (sorted) I–J 0 A–E 1 C–I 1 E–F 1 G–H 1 B–D 2 C–J 2 D–E 2 D–H 2 A–D 4 B–C 4 C–H 4 G–I 4 A–B 5 D–F 5 H–I 6 F–G 7 D–G 11 uf forest (arrow → parent) A B C D E F G H I J |T|=0, weight=0

new UnionFind(10): each vertex is its own set (10 roots). T is empty.

A B C D E F G H I J edges (sorted) I–J 0 ✓ A–E 1 C–I 1 E–F 1 G–H 1 B–D 2 C–J 2 D–E 2 D–H 2 A–D 4 B–C 4 C–H 4 G–I 4 A–B 5 D–F 5 H–I 6 F–G 7 D–G 11 uf forest (arrow → parent) A B C D E F G H I J |T|=1, weight=0

I–J (0): roots I ≠ J → union returns true: the trees merge (I → J) and the edge joins T.

A B C D E F G H I J edges (sorted) I–J 0 ✓ A–E 1 ✓ C–I 1 E–F 1 G–H 1 B–D 2 C–J 2 D–E 2 D–H 2 A–D 4 B–C 4 C–H 4 G–I 4 A–B 5 D–F 5 H–I 6 F–G 7 D–G 11 uf forest (arrow → parent) A B C D E F G H I J |T|=2, weight=1

A–E (1): roots A ≠ E → union returns true: the trees merge (A → E) and the edge joins T.

A B C D E F G H I J edges (sorted) I–J 0 ✓ A–E 1 ✓ C–I 1 ✓ E–F 1 G–H 1 B–D 2 C–J 2 D–E 2 D–H 2 A–D 4 B–C 4 C–H 4 G–I 4 A–B 5 D–F 5 H–I 6 F–G 7 D–G 11 uf forest (arrow → parent) A B C D E F G H I J |T|=3, weight=2

C–I (1): roots C ≠ J → union returns true: the trees merge (C → J) and the edge joins T.

A B C D E F G H I J edges (sorted) I–J 0 ✓ A–E 1 ✓ C–I 1 ✓ E–F 1 ✓ G–H 1 B–D 2 C–J 2 D–E 2 D–H 2 A–D 4 B–C 4 C–H 4 G–I 4 A–B 5 D–F 5 H–I 6 F–G 7 D–G 11 uf forest (arrow → parent) A B C D E F G H I J |T|=4, weight=3

E–F (1): roots E ≠ F → union returns true: the trees merge (F → E) and the edge joins T.

A B C D E F G H I J edges (sorted) I–J 0 ✓ A–E 1 ✓ C–I 1 ✓ E–F 1 ✓ G–H 1 ✓ B–D 2 C–J 2 D–E 2 D–H 2 A–D 4 B–C 4 C–H 4 G–I 4 A–B 5 D–F 5 H–I 6 F–G 7 D–G 11 uf forest (arrow → parent) A B C D E F G H I J |T|=5, weight=4

G–H (1): roots G ≠ H → union returns true: the trees merge (G → H) and the edge joins T.

A B C D E F G H I J edges (sorted) I–J 0 ✓ A–E 1 ✓ C–I 1 ✓ E–F 1 ✓ G–H 1 ✓ B–D 2 ✓ C–J 2 D–E 2 D–H 2 A–D 4 B–C 4 C–H 4 G–I 4 A–B 5 D–F 5 H–I 6 F–G 7 D–G 11 uf forest (arrow → parent) A B C D E F G H I J |T|=6, weight=6

B–D (2): roots B ≠ D → union returns true: the trees merge (B → D) and the edge joins T.

A B C D E F G H I J edges (sorted) I–J 0 ✓ A–E 1 ✓ C–I 1 ✓ E–F 1 ✓ G–H 1 ✓ B–D 2 ✓ C–J 2 ✗ D–E 2 D–H 2 A–D 4 B–C 4 C–H 4 G–I 4 A–B 5 D–F 5 H–I 6 F–G 7 D–G 11 uf forest (arrow → parent) A B C D E F G H I J |T|=6, weight=6

C–J (2): find(C) = find(J) = J → same tree; union returns false: the edge would close a cycle and is skipped.

A B C D E F G H I J edges (sorted) I–J 0 ✓ A–E 1 ✓ C–I 1 ✓ E–F 1 ✓ G–H 1 ✓ B–D 2 ✓ C–J 2 ✗ D–E 2 ✓ D–H 2 A–D 4 B–C 4 C–H 4 G–I 4 A–B 5 D–F 5 H–I 6 F–G 7 D–G 11 uf forest (arrow → parent) A B C D E F G H I J |T|=7, weight=8

D–E (2): roots D ≠ E → union returns true: the trees merge (D → E) and the edge joins T.

A B C D E F G H I J edges (sorted) I–J 0 ✓ A–E 1 ✓ C–I 1 ✓ E–F 1 ✓ G–H 1 ✓ B–D 2 ✓ C–J 2 ✗ D–E 2 ✓ D–H 2 ✓ A–D 4 B–C 4 C–H 4 G–I 4 A–B 5 D–F 5 H–I 6 F–G 7 D–G 11 uf forest (arrow → parent) A B C D E F G H I J |T|=8, weight=10

D–H (2): roots E ≠ H → union returns true: the trees merge (H → E) and the edge joins T.

A B C D E F G H I J edges (sorted) I–J 0 ✓ A–E 1 ✓ C–I 1 ✓ E–F 1 ✓ G–H 1 ✓ B–D 2 ✓ C–J 2 ✗ D–E 2 ✓ D–H 2 ✓ A–D 4 ✗ B–C 4 C–H 4 G–I 4 A–B 5 D–F 5 H–I 6 F–G 7 D–G 11 uf forest (arrow → parent) A B C D E F G H I J |T|=8, weight=10

A–D (4): find(A) = find(D) = E → same tree; union returns false: the edge would close a cycle and is skipped.

A B C D E F G H I J edges (sorted) I–J 0 ✓ A–E 1 ✓ C–I 1 ✓ E–F 1 ✓ G–H 1 ✓ B–D 2 ✓ C–J 2 ✗ D–E 2 ✓ D–H 2 ✓ A–D 4 ✗ B–C 4 ✓ C–H 4 G–I 4 A–B 5 D–F 5 H–I 6 F–G 7 D–G 11 uf forest (arrow → parent) A B C D E F G H I J |T|=9, weight=14

B–C (4): roots E ≠ J → union returns true: the trees merge (J → E) and the edge joins T. |T| = 9 = n − 1 → break.

A B C D E F G H I J edges (sorted) I–J 0 ✓ A–E 1 ✓ C–I 1 ✓ E–F 1 ✓ G–H 1 ✓ B–D 2 ✓ C–J 2 ✗ D–E 2 ✓ D–H 2 ✓ A–D 4 ✗ B–C 4 ✓ C–H 4 G–I 4 A–B 5 D–F 5 H–I 6 F–G 7 D–G 11 uf forest (arrow → parent) A B C D E F G H I J |T|=9, weight=14

return T: 9 edges, total weight 14. A single tree remains. The other 7 edges were never examined.

Minimum Spanning Trees

A Second Example: Airports

2704 867 849 740 621 1846 802 144 187 184 1258 337 1464 1235 1391 946 1090 1121 2342 BOS PVD ORD JFK BWI SFO LAX DFW MIA

Goodrich's example: 9 airports, 19 flight routes (miles). Kruskal's order:

144, 184, 187, 337, 621accept
740 ORD–JFKreject (cycle)
802accept
849, 867reject
946accept
1090, 1121reject
1235 LAX–DFWaccept → 8 edges

Total: 4,456 miles.

Minimum Spanning Trees

Prim's Algorithm

Prim(G, s):
  tree = {s}, T = ∅
  add s's edges to the queue
  while |T| < n − 1:
    (u, v) = lightest edge  // u in tree
    if v in tree: skip       // cycle
    add v to tree, (u, v) to T
    add v's edges leaving the tree
      to the queue
  return T
  • The tree starts from a single vertex and grows by one vertex per step.
  • Each step takes the lightest edge joining a tree vertex to a vertex outside the tree.
  • The lightest edge leaving the tree always belongs to some minimum spanning tree: the tree must connect to the outside somehow, and this edge is the cheapest connection.
  • Similar to Dijkstra; the difference is the queue key: the weight of a single edge, not the distance from the source.
  • Unlike Kruskal, there is always a single tree along the way, so no Union-Find is needed.

Minimum Spanning Trees

Prim in Java (Priority Queue, Lazy Version)

List<int[]> prim(List<List<int[]>> adj, int s) {
  int n = adj.size();                 // adj.get(u): {v, w}
  boolean[] inTree = new boolean[n];
  PriorityQueue<int[]> pq = new PriorityQueue<>(
      (a, b) -> Integer.compare(a[2], b[2]));  // {u, v, w}
  List<int[]> T = new ArrayList<>();
  inTree[s] = true;
  for (int[] e : adj.get(s)) pq.add(new int[]{s, e[0], e[1]});
  while (!pq.isEmpty() && T.size() < n - 1) {
    int[] e = pq.poll();               // lightest candidate
    if (inTree[e[1]]) continue;       // stale: closes a cycle
    inTree[e[1]] = true;
    T.add(e);
    for (int[] f : adj.get(e[1]))
      if (!inTree[f[0]]) pq.add(new int[]{e[1], f[0], f[1]});
  }
  return T;
}
  • The queue holds entries {u, v, w}: u is in the tree, v is a candidate.
  • Lazy version: when v joins the tree, old entries leading to v are not removed; when polled, they are skipped with continue (stale entries).
  • Each edge enters the queue at most once (from whichever end joins the tree first; when the other end joins, the first end is already in the tree): at most m inserts and removals, each O(log m).
  • Total O(m log m) = O(m log n); extra memory O(m).

Minimum Spanning Trees

Prim's Algorithm Step by Step (from A)

priority queue A–E = 1 A–D = 4 A–B = 5 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Tree vertex 1 / 10 Total weight 0

Start: only A is in the tree. A's edges enter the queue: A–E 1, A–D 4, A–B 5. Ties are broken alphabetically.

priority queue E–F = 1 E–D = 2 A–D = 4 A–B = 5 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Tree vertex 2 / 10 Total weight 1

A–E (1): lightest edge leaving the tree → E joins the tree. New candidates: E–F, E–D.

priority queue E–D = 2 A–D = 4 A–B = 5 F–D = 5 F–G = 7 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Tree vertex 3 / 10 Total weight 2

E–F (1): lightest edge leaving the tree → F joins the tree. New candidates: F–D, F–G.

priority queue D–B = 2 D–H = 2 A–D = 4 A–B = 5 F–D = 5 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Tree vertex 4 / 10 Total weight 4

E–D (2): lightest edge leaving the tree → D joins the tree. New candidates: D–B, D–H, D–G. Struck-out entries are now stale (both ends in the tree); they will be skipped when polled.

priority queue D–H = 2 B–C = 4 A–D = 4 A–B = 5 F–D = 5 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Tree vertex 5 / 10 Total weight 6

D–B (2): lightest edge leaving the tree → B joins the tree. New candidates: B–C.

priority queue H–G = 1 B–C = 4 H–C = 4 A–D = 4 A–B = 5 F–D = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Tree vertex 6 / 10 Total weight 8

D–H (2): lightest edge leaving the tree → H joins the tree. New candidates: H–G, H–C, H–I.

priority queue B–C = 4 H–C = 4 A–D = 4 G–I = 4 A–B = 5 F–D = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Tree vertex 7 / 10 Total weight 9

H–G (1): lightest edge leaving the tree → G joins the tree. New candidates: G–I. Struck-out entries are now stale (both ends in the tree); they will be skipped when polled.

priority queue C–I = 1 C–J = 2 H–C = 4 A–D = 4 G–I = 4 A–B = 5 F–D = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Tree vertex 8 / 10 Total weight 13

B–C (4): lightest edge leaving the tree → C joins the tree. New candidates: C–I, C–J.

priority queue I–J = 0 C–J = 2 H–C = 4 A–D = 4 G–I = 4 A–B = 5 F–D = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Tree vertex 9 / 10 Total weight 14

C–I (1): lightest edge leaving the tree → I joins the tree. New candidates: I–J.

priority queue C–J = 2 H–C = 4 A–D = 4 G–I = 4 A–B = 5 F–D = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Tree vertex 10 / 10 Total weight 14

I–J (0): lightest edge leaving the tree → J joins the tree. 9 = n − 1 edges: done, total weight 14.

priority queue C–J = 2 H–C = 4 A–D = 4 G–I = 4 A–B = 5 F–D = 5 H–I = 6 F–G = 7 D–G = 11 0 1 1 1 1 2 2 2 2 4 4 4 4 5 5 6 7 11 A B C D E F G H I J Tree vertex 10 / 10 Total weight 14

Prim's tree: A–E, E–F, E–D, D–B, D–H, H–G, B–C, C–I, I–J. The same total weight (14) as Kruskal. If equal weights come out in another order (e.g. H–C instead of B–C), the tree may differ but still weighs 14.

Minimum Spanning Trees

Kruskal vs. Prim

KruskalPrim
Ideatries edges from lightest to heaviest, merging treesgrows a single tree from one vertex
Along the waya forest (several trees)a single tree
Data structuressorted edge list + Union-Findpriority queue + marker array, adjacency list
Cycle checkfind(u) == find(v)is v already in the tree?
CostO(m log n) (sorting dominates)O(m log n) (with a heap); O(n²) with a matrix
When?sparse graph, edges already in a listadjacency list at hand; O(n²) version for very dense graphs
Both are greedy and find the same total weight on the same graph (14 in the example). With equal edge weights, the trees they find may differ.

Lab · Java code step by step

Lab: Minimum Spanning Trees

Summary

Summary

TermsVertex, edge, degree, direction, weight, path, cycle, and connected component.
RepresentationsAn adjacency list for sparse graphs; a matrix for fast edge queries.
AlgorithmStructureTime (with a list)What it gives
BFSqueueO(n + m)level order, unweighted shortest paths
DFSstack / recursionO(n + m)depth-first traversal, cycles, components
Topologicalqueue / DFSO(n + m)dependency order for a DAG
Dijkstrapriority queueO((n + m) log n)weighted shortest paths (weights ≥ 0)
Kruskalsorting + Union-FindO(m log n)minimum spanning tree
Primpriority queueO(m log n)minimum spanning tree
The data structure shapes the algorithm: BFS uses a queue, DFS a stack, and Dijkstra and Prim a priority queue; topological sorting is defined only for DAGs.

Summary

Interactive Simulators

Undirected Graph SimulatorAdd and remove vertices and edges, run BFS and DFS, see connected components, and view the adjacency list and matrix.
Directed Graph SimulatorArrowed edges, in- and out-degrees, and directed traversal.
Dijkstra's Algorithm SimulatorWatch shortest paths in a weighted graph being found step by step.
Application: Course Prerequisite Planner → Plans courses semester by semester with Kahn's algorithm and catches prerequisite cycles.

Summary

Sample Problems

  1. At most how many edges can a simple undirected graph on 10 vertices have? If the degrees add up to 30, how many edges are there?
  2. Which representation would you use for a road network with n = 10,000 vertices and m = 30,000 edges? Why?
  3. Run BFS and DFS on the traversal example graph starting from H (neighbors in alphabetical order).
  4. If we write DFS with a queue instead of a stack, which algorithm do we get?
  5. In the weighted shortest-path example, if edge B → C weighed 1 instead of 5, what would the shortest path from A to D be?
  6. Why was edge C–J rejected in the Kruskal example?
  7. If Prim starts from J instead of A on the same graph, what are the first three edges chosen and the total weight?
Show answers
  1. 10·9/2 = 45 edges; Σ deg = 2m → m = 15.
  2. An adjacency list: O(n + m) ≈ 40,000 entries; a matrix would need 10⁸ cells, most of them 0.
  3. BFS: H D C G B E F A I · DFS: H D C B A F G E I.
  4. The visit order becomes BFS (the front of the queue is processed instead of the top of the stack).
  5. The path to C does not change (2 < 4 + 1); for D, A → C → E → D = 2 + 3 + 4 = 9, still 9.
  6. C–I and I–J had already been chosen; C and J are in the same set, so the edge would close the cycle C–I–J–C.
  7. J–I (0), I–C (1); C–J (2) comes out stale and is skipped; the third edge is C–B (4). The start vertex does not change the total: still 14.

Summary

Exercises

  1. Undirected edges (0,1), (0,2), (1,3), (2,3), (3,4), (4,5): what are the BFS order and distances from 0? (neighbors in increasing order)
  2. How many cells of this graph's adjacency matrix are 1? How many entries does its adjacency list hold in total?
  3. For the digraph 0→1, 1→2, 2→0, 2→3, while examining which edge does hasCycle(0) return true?
  4. How many topological orders does the DAG 0→1, 0→2, 1→3, 2→3 have? List them.
  5. If prev = [-1, 0, 0, 1, 3], what is the path from 0 to 4? From 2 to 4?
  6. Is a 5-vertex cycle (C5) bipartite? How many connected components does it have?
  7. In lazy Prim, at most how many entries enter the queue? When does an entry become stale?
Answers / hints
  1. Order 0 1 2 3 4 5; distances 0, 1, 1, 2, 3, 4.
  2. 2m = 12 cells (symmetric); the list also holds 2m = 12 entries.
  3. On edge 2 → 0: 0 is gray (on the path) → back edge.
  4. Two: 0 1 2 3 and 0 2 1 3.
  5. 0 → 1 → 3 → 4. The prev chain never passes through 2: it is the path tree built from source 0, not from 2.
  6. No (odd-length cycle). It is connected: 1 component.
  7. Each edge at most once: at most m entries. An edge is added only from whichever end joins the tree first; when the other end joins, the first end is already in the tree (the !inTree[f[0]] check). An entry becomes stale once its target vertex joins the tree.

Summary

Further Reading

  • Goodrich, Tamassia, and Goldwasser, Data Structures and Algorithms in Java, 6th ed., 2014.
  • Cormen, Leiserson, Rivest, and Stein, Introduction to Algorithms, 4th ed., 2022.
  • Sedgewick and Wayne, Algorithms, 4th ed., 2011.

The End

Chapter 14: Graphs
Study questions · Simulators: Undirected Graph Simulator, Directed Graph Simulator, Dijkstra's Algorithm Simulator, Course Prerequisite Planner · Glossary
1 / 1 Sercan KÜLCÜ. All rights reserved.