Two parameters

Where two searches should stop

Search from both ends of a shortest-path query at once and the two frontiers meet somewhere in the middle, having expanded about two thirds of what one search would. Stop at the first vertex both searches have finished, and on five of forty weighted grids the path returned is longer than the shortest. The rule that is always right stops on a different condition, and on one of those grids it also stops sooner.

A shortest-path search from a source spreads outwards until it reaches the goal, and on a graph that looks roughly like a plane — a grid, a road network — the region it covers is roughly a disc whose radius is the distance to the goal. Two searches, one from each end, each need only reach the middle. Two discs of half the radius cover half the area of one disc of the whole radius, so a search from both ends should do about half the work.

That argument is correct as far as it goes and it leaves out the part that decides whether the method is correct at all: when to stop. A one-ended search stops when it takes the goal off its queue, and Dijkstra’s argument guarantees the goal’s distance is final at that moment. That guarantee is the whole of what the bound with a precondition measured Dijkstra’s algorithm buying with its non-negative arcs: a vertex taken off the queue is never reached more cheaply later, so the first moment the goal is taken off is the moment its answer is known. A two-ended search has no single moment like that. The two searches meet — some vertex is reached by both — long before either could be sure the best route passes through that vertex.

This page measures two stopping rules on many grids, and measures what the method actually saves once the rule is the correct one.

The method is old, and so is the trap. Two-ended search for shortest paths was proposed early in the history of the problem, and the incorrect stopping rule has been rediscovered and written down as correct many times since, because it is the rule the picture of two expanding discs suggests. It is a good example of an algorithm whose idea is simple and whose termination condition is where all of the subtlety lives — the same shape as a boundary that costs nothing finds in a different field, where the interesting cost of a method is entirely at the edge the description skips.

Two searches on a grid where every step costs one

The two searches alternate: at each step, whichever frontier has the smaller key expands its next vertex. The forward search runs Dijkstra from the source on the graph’s arcs; the backward search runs Dijkstra from the goal on the arcs reversed. Every vertex therefore carries two distances, one from each end, each final once its own search has taken it off its queue.

The same 30 × 30 grid searched two ways: 543, 428 cells expandedA grid with 254 cells removed, searched from the top-left corner to the bottom-right and shaded by the order in which each cell was taken off the queue — pale early, dark late. No estimate: 543 cells expanded, a path of 58. Two-ended, correct stop: 428 cells expanded, a path of 58. The shortest path costs 58.No estimate543 cells expanded · path 58Two-ended, correct stop428 cells expanded · path 58V = 900, E = 895, every edge costs onethe estimate is a function of the vertex, supplied by the caller
Fig. 1 A 30 × 30 grid with 254 cells removed and every step costing one, searched from the top-left corner to the bottom-right. Dijkstra from one end expands 543 cells. Two Dijkstra searches, one from each end, with the stopping rule described below, expand 428 between them and return the same path of 58. The shading shows two smaller regions, one grown from each corner, pale where each began.

The saving is real and it is not a half. On this grid the two-ended search expands 428 cells to one-ended Dijkstra’s 543 — about 79% — and the reason is visible in the shading. The walls make the grid a poor imitation of a plane: a disc grown from a corner in a maze is shaped by the corridors, and the two half-discs are not half the area of the whole because the region near the goal happens to be sparsely connected. The geometric argument describes an open plane; a maze is not one.

The same 50 × 50 grid searched two ways: 1572, 1121 cells expandedA grid with 699 cells removed, every step into an open cell costing between one and nine, searched from the top-left corner to the bottom-right and shaded by the order in which each cell was taken off the queue — pale early, dark late. No estimate: 1572 cells expanded, a path of 356. Two-ended, correct stop: 1121 cells expanded, a path of 356. The shortest path costs 356.No estimate1572 cells expanded · path 356Two-ended, correct stop1121 cells expanded · path 356V = 2500, E = 2532, steps cost one to ninethe estimate is a function of the vertex, supplied by the caller
Fig. 2 A larger grid, 50 × 50, with steps costing between one and nine. One-ended Dijkstra expands 1,572 cells for a path of 356; the two-ended search expands 1,121, about 71%. On terrain the saving is larger than on the unit maze and still well short of half.

On a larger weighted grid the ratio is 71%. The ratio moves around from grid to grid, which is why the page reports it over many grids below rather than from one plate. But no grid gets close to the half the argument promised, and the reason for that is not the geometry at all. It is the stopping rule.

The obvious rule, and a grid where it is wrong

The obvious rule is the one anyone would write first: stop the first time some vertex has been taken off both queues, and report the forward distance plus the backward distance through that vertex. At that moment the vertex’s distance from the source is final and its distance to the goal is final, so the route through it is a real route and its cost is exactly known.

It is a real route. It need not be the shortest.

3 searches on one grid: from 636 cells expanded down to 441One 30 × 30 grid with 28% of its cells removed, every step costing between one and nine, searched from corner to corner. The shortest path costs 208. No estimate: 636 expanded · path 208. Two-ended, correct stop: 441 expanded · path 208. Two-ended, stop on meeting: 468 expanded · path 212, shortest 208.cells expandedNo estimate636 expanded · path 208Two-ended, correct stop441 expanded · path 208Two-ended, stop on meeting468 expanded · path 212, shortest 208V = 900, steps cost one to nine, seed 7shortest path 208
Fig. 3 A 30 × 30 grid with steps costing one to nine, from a stated seed. One-ended Dijkstra expands 636 cells and finds the shortest path, of 208. The two-ended search with the correct stopping rule expands 441 and also returns 208. The two-ended search that stops at the first vertex both searches have finished expands 468 — more — and returns a path of 212.

The failing case has a simple shape. The shortest path from source to goal crosses the middle along some arc (u,v)(u, v), where uu is on the forward side and vv on the backward side. The forward search finishes uu; the backward search finishes vv; but neither finishes the other’s vertex before some different vertex xx, off the shortest path, happens to be finished by both. The obvious rule stops at xx and reports df(x)+db(x)d_f(x) + d_b(x), which is the length of a real route through xx — and the route df(u)+w(u,v)+db(v)d_f(u) + w(u,v) + d_b(v) through the arc between the two frontiers was shorter and was never compared.

Nothing about the individual searches is wrong. Every distance either search finalised is correct. The error is entirely in the claim that the first doubly-finished vertex lies on a shortest path, which is a claim about the interaction of two correct searches and is false.

The same 30 × 30 grid searched two ways: 441, 468 cells expandedA grid with 243 cells removed, every step into an open cell costing between one and nine, searched from the top-left corner to the bottom-right and shaded by the order in which each cell was taken off the queue — pale early, dark late. Two-ended, correct stop: 441 cells expanded, a path of 208. Two-ended, stop on meeting: 468 cells expanded, a path of 212. The shortest path costs 208.Two-ended, correct stop441 cells expanded · path 208Two-ended, stop on meeting468 cells expanded · path 212V = 900, E = 937, steps cost one to ninethe estimate is a function of the vertex, supplied by the caller
Fig. 4 The two rules on that grid, shaded by expansion order. The correct rule stops after 441 expansions with the shortest path, 208. The meeting rule continues to 468 expansions, until some vertex has been finished by both searches, and reports the route through that vertex, 212. The two panels differ only in the last few dozen expansions, near where the frontiers touch.

That the wrong rule also expanded more cells on this grid is worth pausing on, because it is the opposite of what a shortcut that trades correctness for speed usually does. The meeting rule waits for a vertex to be finished twice, and on terrain two frontiers can overlap considerably before any single vertex is at the head of both queues. The correct rule, below, can stop before any vertex has been finished by both searches — so it is not merely a stricter version of the obvious one. It is a different condition.

The rule that is always right

The correct rule keeps one extra number, μ\mu: the cost of the best complete route seen so far. Whenever either search relaxes an arc (u,v)(u, v) where the far endpoint already has a distance from the other search, it checks whether df(u)+w(u,v)+db(v)d_f(u) + w(u,v) + d_b(v) beats μ\mu, and if so updates it. That captures every route that crosses between the two frontiers along an arc, including the one the obvious rule missed.

The search stops when the smallest key on the forward queue plus the smallest key on the backward queue is at least μ\mu. At that moment every vertex not yet finished by the forward search is at least the forward key away from the source, and every vertex not yet finished by the backward search is at least the backward key away from the goal, so any route through an unexplored part of the graph costs at least their sum — which is at least μ\mu. No undiscovered route can beat the best discovered one, and μ\mu is the answer.

The rule’s correctness argument is about the frontiers, not about any vertex, which is why it can fire before any vertex is finished twice and why the obvious rule’s reasoning does not transfer to it. It is worth writing down the invariant it maintains, because that is what a check can test: at every step, μ\mu is the cost of some real route, and every route whose cost is below the sum of the two smallest keys has already been seen. The first half makes μ\mu an upper bound on the answer and the second makes the sum a lower bound on anything unseen, so the moment the lower bound reaches the upper bound, they are equal. It is also the same kind of argument an estimate is a reweighting needed for reopening: a search may only commit to an answer when it can prove that nothing it has not yet examined could improve it, and “the frontiers met” is not such a proof.

Forty grids, and how often the obvious rule is wrong

One counterexample establishes that the obvious rule is not correct. It does not say how often it matters, and a rule that is wrong on one grid in ten thousand is a different engineering proposition from one wrong on one in ten.

Stopping where the two searches meet returned a longer path on 5 of 40 grids; the correct rule, on none40 grids of 900 cells with 28% removed and steps costing one to nine, each searched from both ends at once. Each column is one grid, and each bar is how much longer than the shortest path the returned path is. Stopping at the first vertex both searches have finished returned a longer path on 5 of them, by 4, 2, 3, 2, 1. Stopping when the two frontiers' smallest keys together reach the best route seen across any arc returned the shortest path on every one, expanding on average 64% of the cells a one-ended search expands.stop where they meetextra path length42321stop when keys reach itextra path length110203040grid, by seedV = 900, 28% blocked, steps cost one to nine5 of 40 wrong under the meeting rule
Fig. 5 Forty 30 × 30 grids with 28% of cells removed and steps costing one to nine, each searched from both ends with both rules. Each column is one grid and each bar is how much longer than the shortest path the returned path was. Stopping where the searches meet returned a longer path on 5 of the 40, by 4, 2, 3, 2 and 1. The correct rule returned the shortest path on all 40, expanding on average 64% of what one-ended Dijkstra expands.

Five grids in forty — one in eight — and the errors are small: one to four units on paths of about two hundred, so at most two per cent. That combination is exactly what makes the bug survive. A test that checked a two-ended search against a one-ended one on a few grids would pass most of the time; a test that checked whether the path was roughly as short would pass always. The errors are rare enough to miss and small enough to excuse.

Stopping where the two searches meet returned a longer path on 0 of 40 grids; the correct rule, on none40 grids of 900 cells with 28% removed, each searched from both ends at once. Each column is one grid, and each bar is how much longer than the shortest path the returned path is. Stopping at the first vertex both searches have finished returned a longer path on 0 of them. Stopping when the two frontiers' smallest keys together reach the best route seen across any arc returned the shortest path on every one, expanding on average 75% of the cells a one-ended search expands.stop where they meetextra path lengthstop when keys reach itextra path length110203040grid, by seedV = 900, 28% blocked0 of 40 wrong under the meeting rule
Fig. 6 The same forty grids with every step costing one. Stopping where the searches meet returned the shortest path on all 40, and so did the correct rule, which expanded on average 75% of what one-ended Dijkstra did. On these unit-cost grids the obvious rule never showed its error.

On unit-cost grids the obvious rule was never wrong in forty tries. That is the plate a test suite built from unweighted mazes would produce, and it would certify the wrong rule. The failure needs arcs of different costs, so that the route through the doubly-finished vertex and the route across the frontier can differ by less than a step — which on these grids never happens when every step costs one, and which says nothing about unweighted graphs in general, only about these.

Stopping where the two searches meet returned a longer path on 14 of 80 grids; the correct rule, on none80 grids of 900 cells with 15% removed and steps costing one to nine, each searched from both ends at once. Each column is one grid, and each bar is how much longer than the shortest path the returned path is. Stopping at the first vertex both searches have finished returned a longer path on 14 of them, by 4, 1, 2, 2, 1, 2, 1, 1, 1, 2, 4, 2, 1, 3. Stopping when the two frontiers' smallest keys together reach the best route seen across any arc returned the shortest path on every one, expanding on average 71% of the cells a one-ended search expands.stop where they meetextra path length41221211124213stop when keys reach itextra path length11020304050607080grid, by seedV = 900, 15% blocked, steps cost one to nine14 of 80 wrong under the meeting rule
Fig. 7 Eighty weighted grids with 15% of cells removed instead of 28%. Stopping where the searches meet returned a longer path on 14 of the 80, by between one and four; the correct rule returned the shortest on every one, expanding on average 71% of what one-ended Dijkstra expanded. More open grids, more places for the two frontiers to touch along different arcs, and a higher failure rate.

With fewer walls the failure rate rises to one grid in six. An open grid has more arcs between the two frontiers at the moment they meet, so more chances for the best route to cross along an arc neither search has finished at both ends. That gives the error a direction worth knowing: the obvious rule is most dangerous on exactly the graphs where two-ended search is most attractive, the open, plane-like ones.

What a test of this would have to contain

Put together, the three sweeps say what a test that can catch the obvious rule needs, and it is more specific than “a few random graphs”.

It needs weighted arcs, because on these grids unit costs never exposed the error in forty tries. It needs many instances, because even on weighted grids the rule is right seven times in eight. It needs an exact comparison with a one-ended search, because the errors are one to four units on paths of two hundred, and any tolerance wider than a single unit misses most of them. And it benefits from open graphs, where the error rate roughly doubles.

A test written by someone who believes the obvious rule is correct is unlikely to contain all four. That is the practical content of this collection’s rule that a check must be shown to reject: the site’s check for the correct rule runs it against one-ended Dijkstra on twenty grids of each kind, and a separate check runs the obvious rule on seed seven and requires it to return 212 rather than 208. If a future change made the obvious rule pass that check, the check would fail, which is the only evidence that the check is testing anything.

Counting on a graph fixed this field’s habit of reporting work on named graph families with seeds stated, and this is a case where the habit is load-bearing for correctness as well as for cost. The rule is wrong on a named seed, and anyone can reproduce the 212.

Why the saving is not a half

Across the sweeps the correct rule expands 64% to 79% of what one search does. Three things account for the gap to a half, and only one of them is geometry.

The frontiers overshoot. The correct rule cannot stop at the moment the frontiers touch; it must wait until their smallest keys sum to μ\mu. By then each search has expanded a ring of vertices beyond the meeting point whose distances are below its own key, so the two regions overlap rather than meeting at a line.

Alternation is by key, not by size. The search expands whichever frontier has the smaller key, which balances the two searches’ radii rather than the number of vertices each has expanded. Where one end of the query sits in a dense region and the other in a sparse one, balanced radii can mean very unbalanced work.

And the plane is not a plane. A maze channels a search along corridors, and a disc of half the radius in a maze need not hold half the cells of a disc of the whole radius. The saving depends on how the number of vertices within a distance grows with the distance, which is a property of the graph, and a half is what that growth looks like only in two dimensions without obstacles.

A constant factor is the whole of the benefit here, and it varies by graph between a fifth and a third. An estimate borrowed from an easier problem found landmark estimates cutting the same kind of search to between a sixth and a fifteenth of Dijkstra’s work, which puts the two techniques’ constants side by side: a good estimate is worth far more than a second search, and it costs preprocessing where a second search costs nothing but the stopping rule. What O-notation does not say is this collection’s account of why that is not a detail: the two-ended search is in the same class as the one-ended one, its entire justification is the constant, and the constant is a measurement rather than a derivation.

Why this belongs next to the reweighting

The obvious stopping rule fails for the same structural reason the non-reopening search in an estimate is a reweighting failed. Both commit to an answer on local evidence — a vertex finished, a vertex finished twice — when the property that would justify committing is a bound over everything not yet examined. Both are correct on unit-cost grids and wrong on weighted ones. And both are the kind of error a check must reject exists for: an assertion comparing the two-ended search against the one-ended one is only worth having if it is run on an instance where the wrong rule fails, and the site’s check names seed seven for exactly that reason.

The invariant that was wrong for seven years is the long form of the lesson. An invariant that holds on every input a test suite contains can be a different invariant from the one an algorithm needs, and the difference is invisible until an input separates them. Here the two invariants are “the first doubly-finished vertex is on a shortest path”, which holds on every unit grid above, and “no unexplored route can beat μ\mu”, which is the one that is true.

What the measurement leaves out

No estimate. Both searches here are plain Dijkstra. Combining two-ended search with A* — an estimate pointing each search at the other end — is the obvious next step and a less obvious one, since each search’s estimate reweights the graph differently and the stopping rule above assumes both searches measure distance on the same arcs.

Alternation by smallest key. Other policies — alternate strictly, or expand whichever frontier is smaller in cells — change the constant and not the correctness, provided the stopping condition is kept. The key-balanced policy is the one whose stopping argument is simplest.

Grids only. A road network is much more plane-like than a maze and the saving there is closer to the geometric one; a social graph, where the number of vertices within a distance grows exponentially, can make a two-ended search save far more than a half, since two small balls hold far fewer vertices than one ball of twice the radius. The factor measured here is a fact about mazes.

Where this ladder goes next: two estimates that must agree

The natural combination of the last three rungs is a two-ended A*: a forward search guided by an estimate of the distance to the goal, and a backward search guided by an estimate of the distance from the source. It is where each of their lessons collides.

By the reweighting, each search is Dijkstra on arcs reduced by its own potential — and the two potentials are different, so the two searches are running on two different graphs. The stopping rule’s argument added a forward key and a backward key and compared them with a route’s cost, which only makes sense if both keys measure the same arcs. With two different potentials they do not, and the correct rule on this page becomes an incorrect rule for the combination.

The known repair is to give both searches the same reweighting, using the average of the two estimates — half the forward estimate minus half the backward one — which is consistent in both directions whenever the two estimates are. The next rung measures three things on the grids above: that the naive combination of two consistent estimates with the stopping rule from this page does return longer paths, how often; that the averaged potential repairs it; and what averaging costs, since each search is now guided by an estimate half as strong as the one it would have alone.

What this makes readable

Essays that name this one as a prerequisite.

Named alongside this one

Essays reaching for the same objects. Nobody chose these; they are what the concept index makes visible.

What links here

Every essay whose body links to this one.

The objects this essay names

Each one links to every other essay that touches it.

Bidirectional searchCheckConstant factorCorrectnessCounterexampleDijkstra's algorithmHeuristic searchInvariantSearch frontierShortest pathStopping ruleVerification