Shortest paths¶
The family melon is built around. All of them are label-setting searches over an updatable heap, all take a length map per arc, and all are configurable through traits — including the semiring, which is what lets the same traversal compute a maximum-reliability or a maximum-capacity path.
dijkstra¶
#include "melon/algorithm/dijkstra.hpp"
for(auto && [v, dist] : dijkstra(graph, length_map, 0u))
std::println("vertex {} at distance {}", v, dist);
Requires outward_incidence_graph, has_vertex_map, and a length map modelling input_mapping<arc_t<G>>. Vertices come out in nondecreasing distance order, each exactly once, with its final distance.
Members.
| Member | Effect |
|---|---|
add_source(s) / add_source(s, d) |
seed a source, optionally at a nonzero distance |
reset() |
clear the search, keep the graph and maps |
finished() / current() / advance() / run() |
the generator protocol |
reached(v) |
v has entered the heap |
visited(v) |
v has been settled |
current_dist(v) |
tentative distance of a reached, unsettled v — needs store_distances |
dist(v) |
final distance — needs store_distances |
pred_arc(v) / pred_vertex(v) |
predecessor — needs store_paths |
path_to(t) |
the arcs of the path, target first — needs store_paths |
Multiple sources are allowed and give the distance to the nearest one; seeding them at different offsets is how you express a weighted multi-source problem.
struct traits : dijkstra_default_traits<static_digraph, double> {
static constexpr bool store_distances = true;
static constexpr bool store_paths = true;
};
dijkstra alg(traits{}, graph, length_map, 0u);
alg.run();
std::println("d(0,4) = {}", alg.dist(4u));
for(auto && a : alg.path_to(4u)) std::print(" {}", a); // 7 5 1
path_to walks predecessors from the target back to the source, so the arcs come out in reverse; reverse the range, or read it as-is when you only need the set.
Prefetching
Before relaxing a vertex, dijkstra issues explicit prefetches for the
out-arc range, the arc targets and the length values it is about to read.
That is what the contiguous_mapping concept is
for; on a map that is not contiguous, the prefetch calls compile to
nothing and the algorithm is simply a normal Dijkstra.
Semirings¶
The relaxation step is semiring::plus(dist_u, length_a) and the comparison semiring::less. Swapping the semiring therefore changes what "shortest" means, without touching the traversal:
| Semiring | plus |
less |
zero |
Computes |
|---|---|---|---|---|
shortest_path_semiring<T> |
+ |
< |
0 |
minimum total length |
most_reliable_path_semiring<T> |
* |
> |
1 |
maximum product of probabilities |
max_capacity_path_semiring<T> |
min |
> |
max() |
widest bottleneck path |
minimum_spanning_tree_semiring<T> |
takes the arc length | < |
0 |
Prim's tree order |
struct reliability_traits {
using semiring = most_reliable_path_semiring<double>;
using heap = updatable_d_ary_heap<
2, std::pair<vertex_t<static_digraph>, double>,
typename semiring::less_t, vertex_map_t<static_digraph, std::size_t>,
views::element_map<1>, views::element_map<0>>;
static constexpr bool store_distances = true;
static constexpr bool store_paths = false;
};
std::vector<double> proba = ...; // one survival probability per arc
dijkstra alg(reliability_traits{}, graph, proba, 0u);
alg.run();
alg.dist(4u); // probability of the most reliable path 0 -> 4
Note that the heap's comparator must be semiring::less_t — the two are not independently chosen, and a mismatch is caught by a static_assert in the algorithm.
Writing your own is four members and two types; anything satisfying the semiring concept in melon/utility/semiring.hpp will do.
Traits¶
dijkstra_default_traits<Graph, ValueType> is the starting point, and inheriting from it and overriding one flag is the normal way to configure:
| Member | Default | Meaning |
|---|---|---|
semiring |
shortest_path_semiring<ValueType> |
see above |
heap |
binary updatable_d_ary_heap keyed by a vertex_map_t<G, std::size_t> |
any updatable_priority_queue with entries std::pair<vertex, length> |
store_distances |
false |
keep a distance per settled vertex |
store_paths |
false |
keep a predecessor arc per reached vertex |
The default heap is worth reading once: its index map is a vertex_map_t<Graph, std::size_t>, so for melon's containers the "where is this vertex in the heap" lookup is an array access rather than a hash. A 4-ary heap is often faster on large sparse graphs — change the first template argument and nothing else.
store_paths also costs a vertex map when the graph has no arc_source (there is no other way back from an arc to its tail), and only an arc-per-vertex map when it does. Both are selected automatically.
bidirectional_dijkstra¶
#include "melon/algorithm/bidirectional_dijkstra.hpp"
bidirectional_dijkstra alg(graph, length_map, 0u, 4u);
auto distance = alg.run();
if(alg.path_found())
for(auto && a : alg.path()) std::print(" {}", a); // 1 5 7
Advances a forward search from the source and a backward search from the target in alternation, stopping when their frontiers meet. On a large graph where you want one distance rather than all of them, this typically explores a small fraction of what a one-sided Dijkstra would.
It requires both outward_incidence_graph and inward_incidence_graph — the backward search walks in-arcs — so it does not accept a static_forward_digraph. It is not a range: run() returns the distance, and path() returns the arcs of the path in order from source to target.
network_voronoi¶
#include "melon/algorithm/network_voronoi.hpp"
std::vector<vertex_t<static_digraph>> kernels = {0u, 4u};
for(auto && [v, entry] : network_voronoi(graph, length_map, kernels)) {
auto && [dist, kernel] = entry;
std::println("{} belongs to {} at distance {}", v, kernel, dist);
}
A multi-source Dijkstra that remembers which source won each vertex: the graph-theoretic Voronoi diagram induced by a set of kernels. Yields (vertex, (distance, kernel)) in nondecreasing distance order.
set_kernels(range) replaces the kernel set on an existing object, so a study over many kernel sets allocates once.
biobjective_dijkstra¶
#include "melon/algorithm/biobjective_dijkstra.hpp"
std::vector<int> blue = ...; // first objective, per arc
std::vector<int> red = ...; // second objective, per arc
biobjective_dijkstra alg(graph, blue, red);
alg.add_source(0u, 0, 0);
alg.run();
for(auto && [b, r] : alg.pareto_front(4u))
std::println("(blue {}, red {})", b, r);
// (blue 20, red 7)
// (blue 27, red 5)
// (blue 28, red 4)
A label-setting algorithm for the bi-objective shortest path problem: instead of one distance per vertex it maintains the set of Pareto-optimal (blue, red) labels, discarding dominated ones as they are generated. pareto_front(v) is the resulting range of nondominated cost pairs, and is_dominated(v, label) answers the query directly.
Both length maps must have the same value type — enforced by a requires clause. The output can be exponentially large in principle; on realistic instances it is not, but there is no cap and no ε-dominance option.
competing_dijkstras¶
#include "melon/algorithm/competing_dijkstras.hpp"
competing_dijkstras alg(graph, blue_length_map, red_length_map);
alg.add_blue_source(s);
alg.add_red_source(t);
alg.run();
Runs two searches with different length maps over the same graph, in one heap, where each vertex is claimed by whichever search reaches it first. add_blue_source and add_red_source seed the two sides, and set_blue_length_map / set_red_length_map swap the maps between runs.
This is the machinery behind "which vertices are strictly closer under one length function than another" — comparing a nominal and a perturbed cost, for instance — computed in a single pass instead of two searches and a subtraction. As with biobjective_dijkstra, both maps must share a value type.
Choosing¶
| Question | Use |
|---|---|
| Distances from one source to everything | dijkstra |
| One source-to-target distance on a big graph | bidirectional_dijkstra |
| Nearest facility, and which one | network_voronoi |
| Trade-off curve between two costs | biobjective_dijkstra |
| Which vertices one cost function reaches first | competing_dijkstras |
| Unweighted hop counts | breadth_first_search |
| Negative arc lengths | not supported — melon has no Bellman–Ford |