Purpose: Find the shortest paths (loops allowed) from to in — asymptotically optimal, and vastly faster than Yen’s when is large.
The Idea
- Compute the shortest-path tree to (Dijkstra on the reversed graph), giving = distance from to .
- Define the sidetrack cost of an edge :
Tree edges have . Any path is the shortest path plus a sequence of sidetrack edges, and its length is . - So the problem becomes: enumerate sets of sidetrack edges in increasing total . This is a -smallest-sums problem.
- Eppstein builds a path graph : a heap-ordered DAG whose nodes are sidetrack edges, with out-degree 4 and where a path from the root corresponds to a valid sidetrack sequence. Running a -smallest search (a heap-based best-first traversal) on yields the answer in .
The heavy machinery is step 3-4: a persistent leftist/ randomized heap per vertex, where each vertex’s heap is its parent’s heap plus its own outgoing sidetracks. Persistence is what keeps the total size rather than .
Simplified practical version
Most contest problems accept a much simpler variant that skips the path-graph construction entirely:
// K shortest s->t walks, loops allowed. d[] = distance to t (reverse Dijkstra).
vector<long long> kShortest(int s, int t, int K) {
vector<long long> res;
vector<int> cnt(n, 0);
priority_queue<pair<long long,int>, vector<pair<long long,int>>, greater<>> pq;
pq.push({d[s], s}); // A*-style: key = g + h
while (!pq.empty() && (int)res.size() < K) {
auto [cost, u] = pq.top(); pq.pop();
if (cnt[u] >= K) continue;
cnt[u]++;
if (u == t) res.push_back(cost);
for (auto [v, w] : adj[u])
pq.push({cost - d[u] + w + d[v], v});
}
return res;
}This is -times-A* with the consistent heuristic : it pops each vertex at most times, and the -th pop of is the -th shortest walk. Simple, correct, and fast enough for typical .
Complexity
| Version | Time | Loops? |
|---|---|---|
| Full Eppstein (path graph) | allowed | |
| A*-with-counters (above) | allowed | |
| Yen | forbidden |
Paradigm
Reduction to a -smallest-sums problem over a heap-ordered structure, plus a potential function () that makes every edge non-negative — the same reweighting trick as Johnson’s algorithm.
Variants / Use Cases
- When you need loopless paths — use Yen; Eppstein’s output may repeat vertices
- K shortest paths in a DAG — trivial DP, no need for either algorithm
- Route planning with alternatives, k-best parses / alignments, network resilience analysis
- Second shortest path () — a single Dijkstra pair (forward and reverse) plus an edge scan is enough; don’t reach for Eppstein