Algorithm Search⌘K

Proofs: Compressed sparse row

These are the human proofs of Compressed sparse row, at full length.

BUILD makes a valid compressed sparse row array and the queries answer for the stored graph full

This proof shows: correctness, termination.

Let EE be the input list of BUILD, of length MM, with distinct pairs (u,v)(u, v), 0≤u,v<n0 \le u, v < n; for an undirected graph it holds each edge in both orders and no loop. For a vertex uu let d(u)d(u) be the number of pairs of EE with first end uu. The structure stands for the graph with an edge (u,v)(u, v), or {u,v}\{u, v\}, for each vv in the row adj[off[u]…off[u+1]−1]\mathit{adj}[\mathit{off}[u] \ldots \mathit{off}[u+1] - 1]. The invariant (C) is (C1) on the offsets, (C2) strictly sorted rows and (C3) symmetry without loops for an undirected graph.

Lemma 1 (offsets). After line L3, off[u]=∑x<ud(x)\mathit{off}[u] = \sum_{x < u} d(x) for 0≤u≤n0 \le u \le n. So (C1) holds and the row of uu has length d(u)d(u).

Proof. After line L2, cnt[x+1]=d(x)\mathit{cnt}[x + 1] = d(x) for 0≤x<n0 \le x < n, and cnt[0]=0\mathit{cnt}[0] = 0. Line L3 sets off[0]=0\mathit{off}[0] = 0 and off[u]=off[u−1]+d(u−1)\mathit{off}[u] = \mathit{off}[u-1] + d(u-1), so by induction on uu the claim holds. Then off[n]=∑xd(x)=M\mathit{off}[n] = \sum_x d(x) = M, the offsets do not decrease, and off[u+1]−off[u]=d(u)\mathit{off}[u+1] - \mathit{off}[u] = d(u). □\square

Lemma 2 (placement). After lines L4 to L6, the row of each uu holds exactly the second ends of the pairs of EE with first end uu, in the order in which these pairs occur in EE.

Proof. We claim that before each step of the loop of line L5, for each uu, pos[u]=off[u]+ku\mathit{pos}[u] = \mathit{off}[u] + k_u, where kuk_u is the number of pairs with first end uu processed so far, and the slots off[u],…,pos[u]−1\mathit{off}[u], \ldots, \mathit{pos}[u] - 1 hold their second ends in order. This holds after line L4, with ku=0k_u = 0. A step on (u,v)(u, v) writes vv at slot pos[u]\mathit{pos}[u] and increases pos[u]\mathit{pos}[u] by 1, so the claim still holds. Since ku≤d(u)k_u \le d(u) and the row of uu has d(u)d(u) slots by Lemma 1, each write stays inside the row of uu, so no write touches another row. At the end ku=d(u)k_u = d(u), so each row is full with its second ends in input order. The loop has MM steps, so it stops. □\square

Lemma 3 (sorting). After line L7 each row is strictly increasing, so (C2) holds.

Proof. Line L7 is done by running lines L1 to L6 first with the second end as the key, which by Lemmas 1 and 2 lists the pairs in non-decreasing order of the second end, and then with the first end as the key on that list. By Lemma 2 the second pass places the pairs of each row in the order of the first list, which is non-decreasing in the second end. The pairs are distinct and have the same first end within a row, so their second ends are distinct, and the row is strictly increasing. □\square

For an undirected graph the rows hold vv in the row of uu exactly when (u,v)∈E(u, v) \in E, which holds exactly when (v,u)∈E(v, u) \in E, and EE has no loop, so (C3) holds. So after BUILD, (C) holds and the stored graph is the graph given by EE.

Theorem. With (C) holding, NEIGHBORS(u)(u) outputs the neighbours of uu in increasing order, DEGREE(u)(u) returns their number, and ADJACENT(u,v)(u, v) returns whether vv is a neighbour of uu. Each of them stops.

Proof. NEIGHBORS reads exactly the row of uu in order, which by (C2) lists each neighbour once in increasing order. DEGREE returns the length of the row, which by (C2) is the number of distinct neighbours.

For ADJACENT, let a=off[u]a = \mathit{off}[u] and b=off[u+1]b = \mathit{off}[u+1], so the row is adj[a…b−1]\mathit{adj}[a \ldots b-1], sorted by (C2). We show that the loop of lines L11 to L13 keeps

(S) a≤lo≤hi≤ba \le \mathit{lo} \le \mathit{hi} \le b, each entry at an index in [a,lo)[a, \mathit{lo}) is less than vv, and each entry at an index in [hi,b)[\mathit{hi}, b) is at least vv.

Line L10 makes (S) true with both ranges empty. In a step, lo≤mid<hi\mathit{lo} \le \mathit{mid} < \mathit{hi}. If adj[mid]<v\mathit{adj}[\mathit{mid}] < v, then since the row is sorted each entry at an index up to mid\mathit{mid} is less than vv, so setting lo=mid+1\mathit{lo} = \mathit{mid} + 1 keeps (S). Otherwise adj[mid]≥v\mathit{adj}[\mathit{mid}] \ge v, and each entry from mid\mathit{mid} on is at least vv, so setting hi=mid\mathit{hi} = \mathit{mid} keeps (S). Each step decreases hi−lo\mathit{hi} - \mathit{lo} by at least 1, so the loop stops, with lo=hi\mathit{lo} = \mathit{hi}. Then by (S) the entries before lo\mathit{lo} are less than vv and the entries from lo\mathit{lo} on are at least vv. If vv is in the row, its index is therefore lo\mathit{lo}, and line L14 returns true. If vv is not in the row, either lo=b\mathit{lo} = b or adj[lo]≠v\mathit{adj}[\mathit{lo}] \ne v, and line L14 returns false. ■\blacksquare

Back to Compressed sparse row