The Algebraic Path Problem
Dijkstra is one solver, the semiring is the knob
Table of Contents
This document is the theoretical companion to the dijkstra explorer tool. See also Dijkstra implementations and the 1D rainwater tool.
1. Thesis
2026-06-12 pass flagged the novelty sub-claim as overstated but the body was never edited. Confirmed via web search: minimax/bottleneck-shortest-path framing for LC407's priority-queue-from-boundary algorithm is standard across public solution write-ups (e.g. algo.monster/liteproblems/407), not original to this document. Body reworded to claim only the explicit (min,max) semiring + shared-solver framing, not the algorithm or its bottleneck characterization. Math, LC407=4, and 6 PBTs re-verified this pass (see below); Knuth-1977/Sobrinho citations unchanged from prior pass.
Dijkstra's algorithm, generalized per Knuth (Knuth 1977), is a schema for solving
the algebraic path problem by greedy frontier expansion, parameterized by a
routing algebra (an ordered semiring). Fix the algebra to (min, +) and you
get shortest paths. Fix it to (min, max) and the same finalize-on-pop schema
computes the lowest-ridge-to-the-boundary level for every interior cell of a
height field, which is 2D Trapping Rain Water (LeetCode 407). Fix it to
(max, min) and you get widest paths.
The priority-queue-from-boundary algorithm for LC 407, and its
characterization as a bottleneck shortest-path problem, are both standard –
documented across public LC 407 write-ups. This document's contribution is
the explicit (min, max) semiring and the algebra-as-data framing shared
across all three instances.
The correctness of the greedy finalize-on-pop invariant has nothing to do with +.
It depends on two structural facts about the algebra:
- ⊕ is selective — combine returns one of its arguments, so a popped node carries a
single finalized value and induces a total/partial order
a ⪯ b ⇔ a ⊕ b = a. - ⊗ is monotone (inflationary) — extending a path cannot make it more preferred:
a ⪯ a ⊗ cfor allc.
Under those, the first time a node is popped its value is final. Everything else is the same loop.
2. The Three Instances as Data
"the only line of code that differs is ⊗" was contradicted by the table directly above it -- ⊕ also flips (min for shortest/bottleneck, max for widest) and the seed set also flips (single source for shortest/widest, boundary cells for bottleneck). Confirmed against src/wal_sh/tools/dijkstra_explorer/core.cljc: the three maps in `algebras` differ in :sel, :seeds, and :extend -- only :seed-key is identical across all three. Body corrected to match the table.
| Algebra | ⊕ (selective) | ⊗ (extend) | val(v) | Seeds |
|---|---|---|---|---|
| shortest-path | min |
acc + h[v] |
min total height along a path from src | single source |
| bottleneck/rainwater | min |
max(acc,h[v]) |
lowest ridge to boundary = water level | boundary cells |
| widest-path | max |
min(acc,h[v]) |
max bottleneck capacity from src | single source |
⊗ differs across all three rows; ⊕ and the seed set differ too, per the
table above – only the solver loop itself stays fixed. The rainwater
scalar is a post-pass: trapped = Σ max(0, level(v) − h[v]).
3. The Two Conditions
Sobrinho (Sobrinho 2002, 2005) isolates the two independent properties:
- Monotonicity (inflationary):
a ⪯ a ⊗ c. Buys termination and finalize-on-pop. The exact analogue of "non-negative weights." - Isotonicity:
a ⪯ b ⇒ (c ⊗ a) ⪯ (c ⊗ b). Buys global optimality — optimal substructure holds.
For (min, max): both hold unconditionally, because max(a, c) ≥ a always.
This is why 2D rainwater needs no Bellman-Ford fallback.
4. Why 1D Rainwater is NOT in This Family
LeetCode 42 (1D) has no Dijkstra content. The graph is a line, so
bottleneck-to-escape collapses to min(prefixMax, suffixMax) − h[i].
No frontier, no priority queue. The 2D problem is the first case where
competing ridges require a selection — which is the work ⊕ does.
5. Reference Implementation
(require '[wal-sh.tools.dijkstra-explorer.core :as d])
;; LC407 -- the canonical test
(let [g d/lc407
final (d/solve g (d/algebra-by-id :bottleneck))]
(d/trapped g final))
The algebra IS data – a map of pure functions:
{:id :bottleneck
:sel :min
:seeds boundary
:seed-key (fn [g c] (nth (:h g) c))
:extend (fn [g acc v] (max acc (nth (:h g) v)))}
The solver is (take-while some? (iterate step (init g A))). Adding a
fourth semiring is a def, not a type.
6. Refutation Conditions
The claim "2D rainwater is Dijkstra over (min, max)" breaks when:
- ⊗ ceases to be monotone – siphoning, evaporation, mid-path drains. Fallback: Bellman-Ford.
- ⊗ ceases to be isotone – composite QoS metrics. Dijkstra terminates but returns non-optimal paths. The silent failure mode.
- ⊕ ceases to be selective – counting semiring
(+, ×). No single finalized value per node. Fallback: Gauss-Jordan / Floyd-Warshall. - The graph degenerates to a line – 1D rainwater. No frontier needed.
7. Property-Based Verification
ran `clojure -M:test` against test/wal_sh/tools/dijkstra_explorer/{core_test,core_prop_test}.cljc directly. 14 tests, 46 assertions, 0 failures. The 6 defspecs (bottleneck-level-geq-height, bottleneck-water-non-negative, bottleneck-flat-zero-water, bottleneck-all-finalized, bottleneck-boundary-no-trap, bottleneck-pop-order-monotone) each ran num-tests=50, totaling 300 -- matches the table's P1-P6 one-to-one. Also reran the LC407 reference block via `bb -cp src`: trapped = 4, matching #+RESULTS: below.
Six PBT properties verified across 300 random grids:
| Property | Assertion |
|---|---|
| P1 | bottleneck level ≥ cell height |
| P2 | trapped water ≥ 0 |
| P3 | flat grids trap zero water |
| P4 | all cells finalized |
| P5 | boundary cells never trap |
| P6 | pop order monotone (non-decreasing) |