Algorithm Search⌘K

Proofs: Eppstein's k shortest paths

These are the human proofs of Eppstein's k shortest paths, at full length.

Eppstein's algorithm lists the k lightest walks full

This proof shows: correctness, termination.

Claim. Let GG have real weights, and let s,ts, t be vertices and k≥0k \ge 0. If a negative cycle lies on some ss-tt walk, EPPSTEIN reports it. Otherwise it ends and outputs min⁡(k,N)\min(k, N) different ss-tt walks, where N≤∞N \le \infty is the number of ss-tt walks, in nondecreasing order of weight, each with its correct weight, such that every ss-tt walk not output weighs at least as much as the last walk output.

Walks are sequences of edges, so parallel edges give different walks.

Step 1: the restriction. Let RR be the set of vertices that are reachable from ss and reach tt. Every vertex of an ss-tt walk is in RR, since the prefix of the walk reaches it from ss and the suffix leads from it to tt. So the ss-tt walks of GG and of G[R]G[R] are the same. Conversely, every cycle CC in G[R]G[R] lies on an ss-tt walk: go from ss to a vertex of CC, around CC, and on to tt. So G[R]G[R] has a negative cycle exactly when a negative cycle lies on an ss-tt walk. Dijkstra's algorithm (when all weights in G[R]G[R] are nonnegative, so no negative cycle exists) or Bellman-Ford on the reversed graph finds the distances d(v)d(v) from each v∈Rv \in R to tt or reports a negative cycle of G[R]G[R], and the report is correct by the correctness of those algorithms. If tt is not reachable from ss, then s∉Rs \notin R, there is no ss-tt walk, and the empty output is correct. From now on assume s∈Rs \in R and no negative cycle in G[R]G[R]; then d(v)d(v) is finite for all v∈Rv \in R.

Step 2: the tree. For v∈R∖{t}v \in R \setminus \{t\}, let next(v)\mathrm{next}(v) be the vertex that last lowered d(v)d(v), and the tree edge the edge used. At the end, d(v)=w(v,next(v))+d(next(v))d(v) = w(v, \mathrm{next}(v)) + d(\mathrm{next}(v)), and the tree edges form a tree into tt: a cycle among them would be created by a strict decrease of a label along it, which forces a negative cycle (the standard predecessor-graph argument for relaxation algorithms). So from every v∈Rv \in R, following tree edges reaches tt after finitely many steps; write P(v)P(v) for this tree path, and note w(P(v))=d(v)w(P(v)) = d(v). Here P(t)P(t) is empty.

Step 3: sidetracks. Every edge e=(u,x)e = (u, x) of G[R]G[R] that is not a tree edge is a sidetrack, with δ(e)=w(u,x)+d(x)−d(u)\delta(e) = w(u, x) + d(x) - d(u). Since d(u)≤w(u,x)+d(x)d(u) \le w(u, x) + d(x) for a distance function, δ(e)≥0\delta(e) \ge 0. Tree edges have w(u,x)+d(x)−d(u)=0w(u, x) + d(x) - d(u) = 0.

For a walk WW from ss to tt, let σ(W)\sigma(W) be the sequence of its non-tree edges, in order. Call a sequence e1,…,eje_1, \ldots, e_j of sidetracks valid if the tail of e1e_1 is on P(s)P(s), and the tail of each ei+1e_{i+1} is on P(head(ei))P(\mathrm{head}(e_i)).

(a) σ\sigma is a bijection from ss-tt walks to valid sequences. Between two consecutive sidetracks, and before the first and after the last, a walk uses only tree edges, and from any vertex there is exactly one tree edge; so the tree part from a vertex yy is a prefix of P(y)P(y). Hence σ(W)\sigma(W) is valid, the last stretch is all of P(head(ej))P(\mathrm{head}(e_j)) (it must end at tt, and PP visits tt only at its end), and WW is determined by σ(W)\sigma(W). Conversely, a valid sequence gives the walk P(s)P(s) up to the tail of e1e_1, then e1e_1, then P(head(e1))P(\mathrm{head}(e_1)) up to the tail of e2e_2, and so on, ending with P(head(ej))P(\mathrm{head}(e_j)); its sequence of non-tree edges is e1,…,eje_1, \ldots, e_j.

(b) w(W)=d(s)+∑iδ(ei)w(W) = d(s) + \sum_i \delta(e_i). Write w(y,z)=(w(y,z)+d(z)−d(y))+d(y)−d(z)w(y, z) = (w(y, z) + d(z) - d(y)) + d(y) - d(z) for each edge of WW. The terms d(y)−d(z)d(y) - d(z) telescope to d(s)−d(t)=d(s)d(s) - d(t) = d(s), and the reduced costs are 0 on tree edges and δ(ei)\delta(e_i) on sidetracks.

Step 4: the heaps. For v∈Rv \in R, the heap Hout(v)H_{\mathrm{out}}(v) has the sidetracks out of vv, the cheapest at the root and the rest heap-ordered below it. HG(t)H_G(t) is built from HGH_G of nothing by inserting the root of Hout(t)H_{\mathrm{out}}(t) (if any), and for v≠tv \ne t, HG(v)H_G(v) is HG(next(v))H_G(\mathrm{next}(v)) with the root of Hout(v)H_{\mathrm{out}}(v) inserted (if any). Line L3 handles vv after next(v)\mathrm{next}(v), so this is well defined, and since the insertion is persistent, HG(next(v))H_G(\mathrm{next}(v)) is not changed. By induction along P(v)P(v), HG(v)H_G(v) is a heap-ordered tree whose nodes are the roots of Hout(u)H_{\mathrm{out}}(u) for the vertices uu of P(v)P(v) (including vv and tt) with nonempty Hout(u)H_{\mathrm{out}}(u). Hang Hout(u)H_{\mathrm{out}}(u) below its root in HG(v)H_G(v). The result, call it Hˉ(v)\bar H(v), is a tree that contains every sidetrack whose tail lies on P(v)P(v) exactly once, and it is heap-ordered: the children in HG(v)H_G(v) have keys at least their parent's by the heap property, and the rest of Hout(u)H_{\mathrm{out}}(u) has keys at least its root's. Every node of Hˉ(v)\bar H(v) has at most four children (two in the leftist heap, two in the array heap).

Step 5: states and walks. A state of the search is (c,x,σ)(c, x, \sigma) where xx is a node of some Hˉ(v)\bar H(v). Define the parent of a state created at line L9 to be the state removed in that iteration, and likewise at line L10. We show by induction over the order of creation that each state (c,x,σ)(c, x, \sigma) satisfies:

(i) σ,e(x)\sigma, e(x) is a valid sequence, and xx is a node of Hˉ(v)\bar H(v) where v=sv = s if σ\sigma is empty and v=headv = \mathrm{head} of the last sidetrack of σ\sigma otherwise;

(ii) c=d(s)+∑e∈σδ(e)+δ(e(x))c = d(s) + \sum_{e \in \sigma} \delta(e) + \delta(e(x)).

For the state of line L5, xx is the root of Hˉ(s)\bar H(s) and σ\sigma is empty, so both hold. A state made at line L9 from (c,x,σ)(c, x, \sigma) has the same σ\sigma and a child yy of xx in the same tree Hˉ(v)\bar H(v), so (i) holds by Step 4, and its cost c−δ(x)+δ(y)c - \delta(x) + \delta(y) satisfies (ii). A state made at line L10 has the sequence σ,e(x)\sigma, e(x) and the root of Hˉ(head(e(x)))\bar H(\mathrm{head}(e(x))), whose sidetrack has its tail on P(head(e(x)))P(\mathrm{head}(e(x))), so (i) holds, and (ii) holds by adding δ\delta of the root.

Now let SS be the set of all states that the rules can ever create (the tree of states under the parent relation, rooted at the state of line L5). The map ϕ(c,x,σ)=(σ,e(x))\phi(c, x, \sigma) = (\sigma, e(x)) is a bijection from SS to the nonempty valid sequences. Onto: given a valid sequence e1,…,eje_1, \ldots, e_j, e1e_1 is at a unique node of Hˉ(s)\bar H(s), reached from the root by a unique sequence of L9 moves; one L10 move goes to the root of Hˉ(head(e1))\bar H(\mathrm{head}(e_1)), where e2e_2 is at a unique node, and so on. One-to-one: a state's history of moves can be read back from it: the number of L10 moves is the length of σ\sigma, the sidetracks of σ\sigma name the nodes where those moves were made, and inside each tree the path from the root to a node is unique. So two different move histories give two different pairs (σ,x)(\sigma, x).

By Step 3 and (ii), the cost of a state is the weight of the walk with the sidetrack sequence ϕ\phi of the state. The tree walk P(s)P(s) is the walk with the empty sequence, of weight d(s)d(s), output at line L4.

Step 6: order of output. All moves have nonnegative cost: an L9 move costs δ(y)−δ(x)≥0\delta(y) - \delta(x) \ge 0 by heap order, and an L10 move costs δ(root)≥0\delta(\text{root}) \ge 0 by Step 3. So a state costs at least as much as its parent. The queue QQ holds, at the start of each iteration of line L6, exactly the states of SS not yet removed whose parent has been removed (and the root, before the first removal): this holds at line L5, and an iteration removes one state and inserts all its children (at most five), none of which has been seen before since each state has one parent. Every state of SS not yet removed has an ancestor in QQ (follow the parent links up to the first one not removed), whose cost is at most its own. Hence the state removed at line L7 costs at most every state not yet removed, and the costs removed form a nondecreasing sequence; each state is removed at most once.

Step 7: conclusion. The output is the tree walk followed by the walks of the removed states, in order. By Step 5 these are different walks, with their correct weights. The tree walk weighs d(s)d(s), which is at most the weight of every walk by Step 3(b). By Step 6 the weights are nondecreasing, and when the loop stops with kk walks output, every walk not output is the walk of a state not removed, which costs at least the last cost removed. If the loop stops because QQ is empty, every state of SS was removed, so every ss-tt walk was output, and NN is finite.

Termination. If N<∞N < \infty, SS is finite and every iteration removes one state, so the loop ends. Otherwise it ends after k−1k - 1 iterations. Lines L1 to L4 are finite. ■\blacksquare

Back to Eppstein's k shortest paths