Bounded-Pruned Yen + A* — interactive visualization

Yen K-shortest paths · Dijkstra vs A* · AllDirectedPaths forward pruning
450 ms/step step 0 / 0

0
0
0
0
Ready.
Pick a mode and an example above, then press Play.

0
0
0
0
Ready.
Pick a mode and an example above, then press Play.

Pane A — paths found

    Pane B — paths found

      source target spur node / current expansion explored / backward-marked forward-marked halo (forward pruning) impossible-spur skip (red strike) Yen-banned edge forward-pruning dropped edge (dashed) forward-pruning kept edge (decoration map) currently considered edge active spur path / reconstructed path accepted path (final answer) Eppstein tree edge T* (δ = 0) δEppstein sidetrack label
      Glossary — expansion, relaxation, heuristic, forward-pruning gate, …
      Expand a node
      Pop the cheapest entry from the priority queue (the open set) and commit its final shortest distance from the source. Once expanded, a node is never reopened (with non-negative weights).
      Relax an edge
      For an edge u → v with weight w, check whether dist[u] + w beats v's current tentative distance. If so, update v and push it into the heap.
      Open set / frontier
      The priority queue of nodes we've discovered but not yet expanded. Its peak size is one measure of memory pressure.
      Heuristic h(v)
      An estimate of the remaining cost from v to the target. A* orders the queue by f(v) = g(v) + h(v) instead of just g(v).
      Admissible heuristic
      Never overestimates the true remaining cost. Required for A* to return an optimal path. The reverse-Dijkstra distance on the original graph is admissible by construction — removing edges or vertices only makes paths longer.
      Spur node (Yen)
      The branching point in Yen's algorithm: the next candidate path follows the previous accepted path up to this node, then deviates.
      Banned edge (Yen)
      Removed from the spur sub-search so the resulting path can't re-create an already-accepted path with the same prefix. Internal nodes of the prefix are also banned to keep the path simple.
      Lower bound (bounded-pruned)
      prefixCost + h[spurNode] — a provable minimum for any path built off this spur. If it already exceeds the cheapest candidate we've found, the spur cannot win and is pruned without running.
      Forward BFS / dF(v)
      Breadth-first walk from the source(s) along outgoing edges. Marks each reachable vertex with dF(v), the minimum number of edges to get there. Sandwich mode draws a cyan dashed halo around every node the forward BFS reaches.
      Backward BFS / dB(v)
      Breadth-first walk from the target(s) along incoming edges. Marks each visited vertex with dB(v), the minimum number of edges to reach a target from there. Drives AllDirectedPaths' edge decoration.
      Decoration map
      The output of the backward BFS: each retained edge is stored with the minimum number of edges from it to a target. The subsequent path-enumeration DFS only traverses edges in this map.
      Orphan branch
      A subgraph that reaches a target but has no incoming edge from any source-reachable vertex. The baseline backward BFS happily decorates these edges; forward pruning drops them because no source → target walk could ever use them.
      Forward-pruning gate
      An edge u → v is retained iff u is forward-reachable from a source AND dF(u) + 1 + dB(v) ≤ budget. Both checks are necessary; together they are sufficient for the edge to lie on at least one feasible source → target walk of length ≤ budget. (Also known as the “sandwich” condition because the edge must fit between forward and backward reachability.)
      What you're watching — algorithm explainer

      What you're watching — K-shortest paths with Yen

      Both panels enumerate the K cheapest simple paths from source (blue) to target (purple), in order of cost.

      Yen's algorithm (the shared backbone)

      1. Find the first shortest path with a single Dijkstra (the initial path) — that's accept #1.
      2. To produce the next path, walk every prefix of the previous accepted path. For each prefix:
        • The last node of the prefix is the spur node.
        • Ban every edge that, taken next, would re-create an already-accepted path sharing this same prefix (Yen rule).
        • Ban every node strictly inside the prefix so the spur can't loop back through it (simple-path rule).
        • Run a shortest-path search from the spur node to the target on this masked graph. prefix + spur path is a candidate.
      3. The cheapest candidate is promoted to the next accepted path. Repeat until you have K.

      Pick any two variants to compare

      The A and B selectors in the controls bar choose the algorithm rendered in each pane. Five variants are wired up, matching the JGraphT JMH grid:

      • Yen + Dijkstra — classical YenKShortestPath: a fresh Dijkstra at every spur, no deferral, no impossible-spur skip.
      • Yen + A* (no bounded prune)BoundedPrunedYenKShortestPath with setBoundedPruning(false). Same task structure as classical Yen but the spur engine is A* using the reverse-Dijkstra heuristic. Most of the headline speedup on the Andorra bench comes from this engine alone.
      • BPYen + Dijkstra — bounded-prune lower-bound queue and impossible-spur skip, but the spur engine itself is plain Dijkstra (no heuristic). Useful for isolating the contribution of the bounded layer from the contribution of the A* engine.
      • BPYen + A* (full) — everything on: A* spur engine, lower-bound deferral, impossible-spur skip.
      • Eppstein — different family. Builds the shortest-path tree from the target, labels every non-tree edge with its sidetrack cost δ(u, v) = w(u, v) + dB(v) − dB(u), then enumerates the k lowest-cost walks from source to target. Walks may repeat vertices, so on graphs with cycles the answers differ from the Yen-family (which returns simple paths).

      What to compare

      Default selection — Yen + Dijkstra (left) vs BPYen + A* (right) — mirrors the original visualization: the right pane is dramatically faster on the chain example (zero spur searches after the initial path) and visibly tighter on the grid (A* heads straight at the target). Set both panes to A* engines (one with the bounded-prune layer, one without) to see how small the bounded layer's marginal win is once A* is in play; set one pane to Eppstein to compare a fundamentally different algorithm.