Spectral and random walks
Random walks turn a graph into a Markov chain, whose stationary distribution is PageRank; the Laplacian matrix encodes the same graph algebraically, and its eigenvectors reveal clusters.
Learning goals
- Write the transition matrix of a random walk and iterate it
- Explain PageRank, damping and dangling pages
- Build the graph Laplacian and read the number of components from it
- Describe how the Fiedler vector splits a graph into clusters
Prerequisites
Imagine a surfer who clicks random links: where do they spend their time? That single question defines PageRank. The same graph seen as a matrix tells you how many pieces it has and where to cut it, using only linear algebra.
Random walk transition
A **random walk** on a directed graph moves from the current vertex u to one of its out-neighbours chosen uniformly at random. The **transition matrix** P has P[u][v] = 1 / outdeg(u) for every edge u → v and 0 otherwise, so every row sums to 1. A vertex with no out-edges (a **dangling** vertex) is handled by letting it jump to any vertex with equal probability.
If dist is a row vector of probabilities over the vertices, one step of the walk gives dist · P. Applying this over and over (**power iteration**) moves the distribution toward a **stationary distribution** π that satisfies π = π · P: once there, one more step changes nothing.
Open the animation with the preset “Pure random walk converges”. The graph 0→1, 1→2, 2→0, 0→2 settles at 0.4, 0.2, 0.4 after a few dozen rounds. The preset “Pure walk on a 2-cycle never settles” shows why a plain walk is not enough: on 0↔1, 0↔2 the distribution alternates between uniform and 2/3, 1/6, 1/6 forever, because the graph is periodic. Damping removes this problem.
1// p[u][v] = probability that a random walker standing at u moves to v next (a vertex without out-edges jumps anywhere)2static double[][] transitionMatrix(int n, List<Integer>[] out) {3double[][] p = new double[n][n];4for (int u = 0; u < n; u++) {5if (out[u].isEmpty()) { Arrays.fill(p[u], 1.0 / n); continue; }6for (int v : out[u]) p[u][v] += 1.0 / out[u].size();7}8return p;9}10static double[] step(double[] dist, double[][] p) { // the distribution after one step: dist * P11int n = dist.length;12double[] next = new double[n];13for (int u = 0; u < n; u++) for (int v = 0; v < n; v++) next[v] += dist[u] * p[u][v];14return next;15}
Random walks and PageRank: power iteration on the transition matrix
Interactive · edit the inputdirected graph · 4 vertices · 5 edges
0.2500
0.2500
0.2500
0.2500
rank of each page
Imagine a surfer who, with probability 0.85, follows a random link of the current page and otherwise jumps to a uniformly random page. The rank of a page is the long-run fraction of time spent there. Power iteration starts from the uniform vector 1/4 and applies the transition rule once per round.
1// out[v] = successors of v; d is the damping factor in (0, 1]; returns the rank vector after the given number of rounds (it sums to 1).2// A vertex without out-edges is "dangling": its rank is shared equally by all vertices.3static double[] pageRank(int n, List<Integer>[] out, double d, int rounds) {4double[] rank = new double[n];5Arrays.fill(rank, 1.0 / n); // start from the uniform distribution6for (int round = 0; round < rounds; round++) {7double[] next = new double[n];8double dangling = 0;9for (int u = 0; u < n; u++) {10if (out[u].isEmpty()) { dangling += rank[u]; continue; }11for (int v : out[u]) next[v] += d * rank[u] / out[u].size(); // u hands its rank equally to its out-neighbours12}13for (int v = 0; v < n; v++) next[v] += d * dangling / n + (1 - d) / n; // dangling mass and teleportation, spread evenly14rank = next;15}16return rank;17}
Live state
n
4
damping
0.85
rounds
30
dangling
0
Execution trace (dry run table)Show 32 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L3 | n=4 damping=0.85 rounds=30 dangling=0 | Imagine a surfer who, with probability 0.85, follows a random link of the current page and otherwise jumps to a uniformly random page. The rank of a page is the long-run fraction of time spent there. Power iteration starts from the uniform vector 1/4 and applies the transition rule once per round. |
| 2 | L14 | round=1 delta=0.6375 sum=1 | Round 1: the ranks still sum to 1. Total change from the previous round: 0.6375. |
| 3 | L14 | round=2 delta=0.541875 sum=1 | Round 2: the ranks still sum to 1. Total change from the previous round: 0.5419. |
| 4 | L14 | round=3 delta=0.460594 sum=1 | Round 3: the ranks still sum to 1. Total change from the previous round: 0.4606. |
| 5 | L14 | round=4 delta=0.195752 sum=1 | Round 4: the ranks still sum to 1. Total change from the previous round: 0.1958. |
| 6 | L14 | round=5 delta=0.083195 sum=1 | Round 5: the ranks still sum to 1. Total change from the previous round: 0.0832. |
| 7 | L14 | round=6 delta=0.070716 sum=1 | Round 6: the ranks still sum to 1. Total change from the previous round: 0.0707. |
| 8 | L14 | round=7 delta=0.060108 sum=1 | Round 7: the ranks still sum to 1. Total change from the previous round: 0.0601. |
| 9 | L14 | round=8 delta=0.025546 sum=1 | Round 8: the ranks still sum to 1. Total change from the previous round: 0.0255. |
| 10 | L14 | round=9 delta=0.010857 sum=1 | Round 9: the ranks still sum to 1. Total change from the previous round: 0.0109. |
| 11 | L14 | round=10 delta=0.009228 sum=1 | Round 10: the ranks still sum to 1. Total change from the previous round: 0.0092. |
| 12 | L14 | round=11 delta=0.007844 sum=1 | Round 11: the ranks still sum to 1. Total change from the previous round: 0.0078. |
| 13 | L14 | round=12 delta=0.003334 sum=1 | Round 12: the ranks still sum to 1. Total change from the previous round: 0.0033. |
| 14 | L14 | round=13 delta=0.001417 sum=1 | Round 13: the ranks still sum to 1. Total change from the previous round: 0.0014. |
| 15 | L14 | round=14 delta=0.001204 sum=1 | Round 14: the ranks still sum to 1. Total change from the previous round: 0.0012. |
| 16 | L14 | round=15 delta=0.001024 sum=1 | Round 15: the ranks still sum to 1. Total change from the previous round: 0.0010. |
| 17 | L14 | round=16 delta=0.000435 sum=1 | Round 16: the ranks still sum to 1. Total change from the previous round: 0.0004. |
| 18 | L14 | round=17 delta=0.000185 sum=1 | Round 17: the ranks still sum to 1. Total change from the previous round: 0.0002. |
| 19 | L14 | round=18 delta=0.000157 sum=1 | Round 18: the ranks still sum to 1. Total change from the previous round: 0.0002. |
| 20 | L14 | round=19 delta=0.000134 sum=1 | Round 19: the ranks still sum to 1. Total change from the previous round: 0.0001. |
| 21 | L14 | round=20 delta=0.000057 sum=1 | Round 20: the ranks still sum to 1. Total change from the previous round: 0.0001 (essentially converged). |
| 22 | L14 | round=21 delta=0.000024 sum=1 | Round 21: the ranks still sum to 1. Total change from the previous round: 0.0000 (essentially converged). |
| 23 | L14 | round=22 delta=0.000021 sum=1 | Round 22: the ranks still sum to 1. Total change from the previous round: 0.0000 (essentially converged). |
| 24 | L14 | round=23 delta=0.000017 sum=1 | Round 23: the ranks still sum to 1. Total change from the previous round: 0.0000 (essentially converged). |
| 25 | L14 | round=24 delta=0.000007 sum=1 | Round 24: the ranks still sum to 1. Total change from the previous round: 0.0000 (essentially converged). |
| 26 | L14 | round=25 delta=0.000003 sum=1 | Round 25: the ranks still sum to 1. Total change from the previous round: 0.0000 (essentially converged). |
| 27 | L14 | round=26 delta=0.000003 sum=1 | Round 26: the ranks still sum to 1. Total change from the previous round: 0.0000 (essentially converged). |
| 28 | L14 | round=27 delta=0.000002 sum=1 | Round 27: the ranks still sum to 1. Total change from the previous round: 0.0000 (essentially converged). |
| 29 | L14 | round=28 delta=0.000001 sum=1 | Round 28: the ranks still sum to 1. Total change from the previous round: 0.0000 (essentially converged). |
| 30 | L14 | round=29 delta=0 sum=1 | Round 29: the ranks still sum to 1. Total change from the previous round: 0.0000 (essentially converged). |
| 31 | L14 | round=30 delta=0 sum=1 | Round 30: the ranks still sum to 1. Total change from the previous round: 0.0000 (essentially converged). |
| 32 | L16 | return=0.3725 0.1958 0.3941 0.0375 ranks=0.3725 0.1958 0.3941 0.0375 | After 30 rounds the ranks are 0.3725, 0.1958, 0.3941, 0.0375. The damping factor makes the walk forget its start, so the iteration converges geometrically at rate 0.85 per round. |
PageRank intuition
**PageRank** models a surfer who, with probability d (the damping factor, classically 0.85), follows a random link from the current page and, with probability 1 − d, jumps to a page chosen uniformly at random. A page's rank is the long-run fraction of time the surfer spends on it: pages with many incoming links from important pages rank high.
The teleportation makes the chain strongly connected and aperiodic, so the stationary distribution exists, is unique and power iteration converges **geometrically at rate d per round**: after 60 rounds with d = 0.85 the error is about 0.85^60 ≈ 6 · 10^-5. Each round hands d · rank[u] / outdeg(u) to every out-neighbour and adds (1 − d) / n (plus the dangling mass) to everyone.
On the animation's default web 0→1, 0→2, 1→2, 2→0, 3→2 with d = 0.85 the ranks after 30 rounds are 0.3725, 0.1958, 0.3941, 0.0375. Page 2 is the best ranked: it has three in-links. Page 3 has no in-links at all, so its rank is exactly the teleportation share (1 − d) / n = 0.15 / 4 = 0.0375. With d = 0.5 page 3 gets 0.125. The dangling-page preset shows the leaked rank being redistributed.
| Problem of the plain walk | Fix in PageRank |
|---|---|
| dangling pages leak probability | their rank is shared by all pages |
| periodic graphs oscillate | teleportation breaks the periodicity |
| several components: no unique answer | teleportation connects everything |
| slow convergence | rate d per round: smaller d is faster but forgets the links |
1// out[v] = successors of v; d is the damping factor in (0, 1]; returns the rank vector after the given number of rounds (it sums to 1).2// A vertex without out-edges is "dangling": its rank is shared equally by all vertices.3static double[] pageRank(int n, List<Integer>[] out, double d, int rounds) {4double[] rank = new double[n];5Arrays.fill(rank, 1.0 / n); // start from the uniform distribution6for (int round = 0; round < rounds; round++) {7double[] next = new double[n];8double dangling = 0;9for (int u = 0; u < n; u++) {10if (out[u].isEmpty()) { dangling += rank[u]; continue; }11for (int v : out[u]) next[v] += d * rank[u] / out[u].size(); // u hands its rank equally to its out-neighbours12}13for (int v = 0; v < n; v++) next[v] += d * dangling / n + (1 - d) / n; // dangling mass and teleportation, spread evenly14rank = next;15}16return rank;17}
Worst
O((V + E) · rounds)
Space
O(V)
Random walks and PageRank: power iteration on the transition matrix
Interactive · edit the inputdirected graph · 4 vertices · 5 edges
0.2500
0.2500
0.2500
0.2500
rank of each page
Imagine a surfer who, with probability 0.85, follows a random link of the current page and otherwise jumps to a uniformly random page. The rank of a page is the long-run fraction of time spent there. Power iteration starts from the uniform vector 1/4 and applies the transition rule once per round.
1// out[v] = successors of v; d is the damping factor in (0, 1]; returns the rank vector after the given number of rounds (it sums to 1).2// A vertex without out-edges is "dangling": its rank is shared equally by all vertices.3static double[] pageRank(int n, List<Integer>[] out, double d, int rounds) {4double[] rank = new double[n];5Arrays.fill(rank, 1.0 / n); // start from the uniform distribution6for (int round = 0; round < rounds; round++) {7double[] next = new double[n];8double dangling = 0;9for (int u = 0; u < n; u++) {10if (out[u].isEmpty()) { dangling += rank[u]; continue; }11for (int v : out[u]) next[v] += d * rank[u] / out[u].size(); // u hands its rank equally to its out-neighbours12}13for (int v = 0; v < n; v++) next[v] += d * dangling / n + (1 - d) / n; // dangling mass and teleportation, spread evenly14rank = next;15}16return rank;17}
Live state
n
4
damping
0.85
rounds
30
dangling
0
Execution trace (dry run table)Show 32 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L3 | n=4 damping=0.85 rounds=30 dangling=0 | Imagine a surfer who, with probability 0.85, follows a random link of the current page and otherwise jumps to a uniformly random page. The rank of a page is the long-run fraction of time spent there. Power iteration starts from the uniform vector 1/4 and applies the transition rule once per round. |
| 2 | L14 | round=1 delta=0.6375 sum=1 | Round 1: the ranks still sum to 1. Total change from the previous round: 0.6375. |
| 3 | L14 | round=2 delta=0.541875 sum=1 | Round 2: the ranks still sum to 1. Total change from the previous round: 0.5419. |
| 4 | L14 | round=3 delta=0.460594 sum=1 | Round 3: the ranks still sum to 1. Total change from the previous round: 0.4606. |
| 5 | L14 | round=4 delta=0.195752 sum=1 | Round 4: the ranks still sum to 1. Total change from the previous round: 0.1958. |
| 6 | L14 | round=5 delta=0.083195 sum=1 | Round 5: the ranks still sum to 1. Total change from the previous round: 0.0832. |
| 7 | L14 | round=6 delta=0.070716 sum=1 | Round 6: the ranks still sum to 1. Total change from the previous round: 0.0707. |
| 8 | L14 | round=7 delta=0.060108 sum=1 | Round 7: the ranks still sum to 1. Total change from the previous round: 0.0601. |
| 9 | L14 | round=8 delta=0.025546 sum=1 | Round 8: the ranks still sum to 1. Total change from the previous round: 0.0255. |
| 10 | L14 | round=9 delta=0.010857 sum=1 | Round 9: the ranks still sum to 1. Total change from the previous round: 0.0109. |
| 11 | L14 | round=10 delta=0.009228 sum=1 | Round 10: the ranks still sum to 1. Total change from the previous round: 0.0092. |
| 12 | L14 | round=11 delta=0.007844 sum=1 | Round 11: the ranks still sum to 1. Total change from the previous round: 0.0078. |
| 13 | L14 | round=12 delta=0.003334 sum=1 | Round 12: the ranks still sum to 1. Total change from the previous round: 0.0033. |
| 14 | L14 | round=13 delta=0.001417 sum=1 | Round 13: the ranks still sum to 1. Total change from the previous round: 0.0014. |
| 15 | L14 | round=14 delta=0.001204 sum=1 | Round 14: the ranks still sum to 1. Total change from the previous round: 0.0012. |
| 16 | L14 | round=15 delta=0.001024 sum=1 | Round 15: the ranks still sum to 1. Total change from the previous round: 0.0010. |
| 17 | L14 | round=16 delta=0.000435 sum=1 | Round 16: the ranks still sum to 1. Total change from the previous round: 0.0004. |
| 18 | L14 | round=17 delta=0.000185 sum=1 | Round 17: the ranks still sum to 1. Total change from the previous round: 0.0002. |
| 19 | L14 | round=18 delta=0.000157 sum=1 | Round 18: the ranks still sum to 1. Total change from the previous round: 0.0002. |
| 20 | L14 | round=19 delta=0.000134 sum=1 | Round 19: the ranks still sum to 1. Total change from the previous round: 0.0001. |
| 21 | L14 | round=20 delta=0.000057 sum=1 | Round 20: the ranks still sum to 1. Total change from the previous round: 0.0001 (essentially converged). |
| 22 | L14 | round=21 delta=0.000024 sum=1 | Round 21: the ranks still sum to 1. Total change from the previous round: 0.0000 (essentially converged). |
| 23 | L14 | round=22 delta=0.000021 sum=1 | Round 22: the ranks still sum to 1. Total change from the previous round: 0.0000 (essentially converged). |
| 24 | L14 | round=23 delta=0.000017 sum=1 | Round 23: the ranks still sum to 1. Total change from the previous round: 0.0000 (essentially converged). |
| 25 | L14 | round=24 delta=0.000007 sum=1 | Round 24: the ranks still sum to 1. Total change from the previous round: 0.0000 (essentially converged). |
| 26 | L14 | round=25 delta=0.000003 sum=1 | Round 25: the ranks still sum to 1. Total change from the previous round: 0.0000 (essentially converged). |
| 27 | L14 | round=26 delta=0.000003 sum=1 | Round 26: the ranks still sum to 1. Total change from the previous round: 0.0000 (essentially converged). |
| 28 | L14 | round=27 delta=0.000002 sum=1 | Round 27: the ranks still sum to 1. Total change from the previous round: 0.0000 (essentially converged). |
| 29 | L14 | round=28 delta=0.000001 sum=1 | Round 28: the ranks still sum to 1. Total change from the previous round: 0.0000 (essentially converged). |
| 30 | L14 | round=29 delta=0 sum=1 | Round 29: the ranks still sum to 1. Total change from the previous round: 0.0000 (essentially converged). |
| 31 | L14 | round=30 delta=0 sum=1 | Round 30: the ranks still sum to 1. Total change from the previous round: 0.0000 (essentially converged). |
| 32 | L16 | return=0.3725 0.1958 0.3941 0.0375 ranks=0.3725 0.1958 0.3941 0.0375 | After 30 rounds the ranks are 0.3725, 0.1958, 0.3941, 0.0375. The damping factor makes the walk forget its start, so the iteration converges geometrically at rate 0.85 per round. |
Graph Laplacian
The **graph Laplacian** of an undirected simple graph is L = D − A, where D is the diagonal matrix of degrees and A the adjacency matrix. Each row sums to 0, so the all-ones vector is an eigenvector with eigenvalue 0. For a path of three vertices L = [[1, −1, 0], [−1, 2, −1], [0, −1, 1]] with eigenvalues 0, 1 and 3.
The Laplacian measures smoothness: for any vector x, xᵀ L x = Σ over edges (x_u − x_v)². This makes L positive semidefinite, with all eigenvalues non-negative, and it explains the main structural fact: **the multiplicity of the eigenvalue 0 equals the number of connected components**. A vector that is constant on each component has zero energy, and nothing else does.
The code builds L and counts the zero eigenvalues as n − rank(L) by Gaussian elimination with partial pivoting. This is not faster than a BFS for counting components, but it shows the algebra at work, and the same matrix gives more: the second-smallest eigenvalue (the **algebraic connectivity**) is positive exactly when the graph is connected and is large for well-connected graphs.
| Fact | Meaning |
|---|---|
| L = D − A | degrees on the diagonal, −1 for every edge |
| x^T L x = Σ (x_u − x_v)² | energy of x over the edges; L is positive semidefinite |
| multiplicity of eigenvalue 0 | number of connected components |
| second-smallest eigenvalue λ₂ | algebraic connectivity; positive iff connected |
1static double[][] laplacian(int n, int[][] edges) { // L = D - A of an undirected simple graph2double[][] l = new double[n][n];3for (int[] e : edges) {4l[e[0]][e[0]]++;5l[e[1]][e[1]]++;6l[e[0]][e[1]]--;7l[e[1]][e[0]]--;8}9return l;10}11static int componentsFromLaplacian(double[][] l) { // the number of zero eigenvalues is n - rank(L), and it equals the number of components12int n = l.length, rank = 0;13double[][] m = new double[n][];14for (int i = 0; i < n; i++) m[i] = l[i].clone();15for (int col = 0; col < n && rank < n; col++) { // Gaussian elimination with partial pivoting16int pivot = rank;17for (int r = rank + 1; r < n; r++) if (Math.abs(m[r][col]) > Math.abs(m[pivot][col])) pivot = r;18if (Math.abs(m[pivot][col]) < 1e-9) continue;19double[] t = m[pivot]; m[pivot] = m[rank]; m[rank] = t;20for (int r = rank + 1; r < n; r++) {21double f = m[r][col] / m[rank][col];22for (int c = col; c < n; c++) m[r][c] -= f * m[rank][c];23}24rank++;25}26return n - rank;27}
Spectral clustering overview
**Spectral clustering** cuts a graph where it is weakly connected, using the eigenvector of the second-smallest Laplacian eigenvalue, the **Fiedler vector**. It minimises Σ (x_u − x_v)² over edges for vectors that are orthogonal to the all-ones vector and have unit length: vertices joined by many edges get similar values. The signs of the Fiedler vector therefore split the vertices into two sides with few edges between them.
To compute it without a linear-algebra library, run **power iteration** on shift · I − L, where shift is at least the largest eigenvalue. That matrix has the same eigenvectors as L with the order of the eigenvalues reversed, so the dominant eigenvector is that of the smallest eigenvalue of L: the all-ones vector. Projecting every iterate orthogonal to the all-ones vector removes it, and the iteration converges to the Fiedler vector. The code does exactly this and returns the side of each vertex.
The verification ran two cliques of 3 to 5 vertices joined by a single edge and checked that the sign split recovers the two cliques; it also checked that a path with an even number of vertices splits into its two halves. For more than two clusters, take the first k eigenvectors, treat each vertex as a point in k dimensions and run k-means. The method has no guarantee of the optimal cut, but the Cheeger inequality bounds how good the sign cut is in terms of the eigenvalue.
1// splits a connected graph in two by the sign of the Fiedler vector: the eigenvector of the second-smallest eigenvalue of L2static boolean[] spectralBisection(int n, int[][] edges) {3double[][] l = laplacian(n, edges);4double shift = 0;5for (int i = 0; i < n; i++) shift = Math.max(shift, 2 * l[i][i]); // shift >= the largest eigenvalue, so shift*I - L has the same eigenvectors, reversed in order6double[] x = new double[n];7Random random = new Random(1);8for (int i = 0; i < n; i++) x[i] = random.nextDouble() - 0.5;9for (int iter = 0; iter < 3000; iter++) { // power iteration on shift*I - L10double mean = 0;11for (double v : x) mean += v / n;12for (int i = 0; i < n; i++) x[i] -= mean; // stay orthogonal to the all-ones vector (eigenvalue 0 of L)13double[] y = new double[n];14for (int i = 0; i < n; i++) {15y[i] = shift * x[i];16for (int j = 0; j < n; j++) y[i] -= l[i][j] * x[j];17}18double norm = 0;19for (double v : y) norm += v * v;20norm = Math.sqrt(norm);21for (int i = 0; i < n; i++) x[i] = y[i] / norm;22}23boolean[] side = new boolean[n];24for (int i = 0; i < n; i++) side[i] = x[i] > 0;25return side;26}
Worst
O(iterations · n²) with a dense matrix
Space
O(n²)
Practice ladder
Now try it yourself
Easy
- Find Center of Star Graph
Medium
- Number of Provinces
Advanced
- Minimum Number of Days to Disconnect Island
Key takeaways
- Transition matrix rows sum to 1; power iteration moves a distribution toward the stationary one.
- PageRank = random surfer with teleportation; it converges at rate d per round and gives a link-less page exactly (1 − d) / n.
- The Laplacian's zero eigenvalues count components; the Fiedler vector's signs give a spectral cut.