Maps, heaps and sets¶
These are the containers the algorithms are built on. They are not graph-specific, and nothing stops you from using them on their own — but their design is driven by what a graph algorithm needs: flat storage indexed by an integer identifier, a priority queue whose entries can be re-prioritized, and a union-find.
static_map¶
A fixed-size array indexed by an integral key: one allocation, no capacity, no growth amortization. This is what create_vertex_map and create_arc_map return for melon's containers, and it is a contiguous_mapping — which is what makes prefetching possible in the shortest-path algorithms.
static_map<unsigned int, double> m(5, 0.0); // size 5, all zero
m[2u] = 1.5;
m.fill(0.0);
m.resize(8); // reallocates; values are not preserved
It is also a random_access_range, so std::ranges algorithms apply to its values directly. static_map<K, bool> is a plain array of bool — one byte per entry, unlike std::vector<bool> — and therefore stays contiguous and gives out real bool& references.
static_filter_map¶
The bit-packed counterpart: one bit per key, with a proxy reference. Use it when the payload is genuinely a predicate over a large key space and the eight-fold memory saving matters more than pointer access.
static_filter_map<unsigned int> keep(8, false);
keep[3u] = true;
keep[5u] = true;
for(auto && k : keep.filter(std::views::iota(0u, 8u)))
std::print(" {}", k); // 3 5
It is an output_mapping but not a contiguous_mapping — bits have no address — so an algorithm that requires contiguity will reject it. The filter member is a convenience for the common "iterate the keys that are set" case, and is exactly what makes it a natural filter for views::subgraph.
Heaps¶
The heaps are described by two concepts in melon/utility/priority_queue.hpp:
template <typename Q>
concept priority_queue = std::semiregular<Q> &&
requires(Q q, typename Q::value_type v) {
q.push(v);
{ q.top() } -> std::same_as<typename Q::value_type>;
q.pop();
{ q.size() } -> std::same_as<typename Q::size_type>;
{ q.empty() } -> std::convertible_to<bool>;
q.clear();
};
template <typename Q>
concept updatable_priority_queue = priority_queue<Q> &&
requires(Q q, typename Q::id_type i, typename Q::priority_type p) {
{ q.contains(i) } -> std::convertible_to<bool>;
{ q.priority(i) } -> std::same_as<typename Q::priority_type>;
q.promote(i, p);
q.demote(i, p);
};
Anything satisfying them can be substituted into an algorithm through its traits — including std::priority_queue for the non-updatable case, or a bucket queue of your own for integer priorities.
d_ary_heap¶
template <std::size_t D, typename Entry,
typename PriorityComparator = std::greater<Entry>,
input_mapping<Entry> EntryPriorityMap = views::identity_map>
class d_ary_heap;
| Parameter | Meaning |
|---|---|
D |
children per node — 2 is a binary heap, 4 tends to win on large workloads |
Entry |
the element type stored |
PriorityComparator |
strict weak order on priorities; the element that compares before all others is on top |
EntryPriorityMap |
how to get an entry's priority; the default is the entry itself |
d_ary_heap<2, int> max_heap; // std::greater -> largest on top
for(int e : {0, 7, 3, 5, 6, 11}) max_heap.push(e);
max_heap.top(); // 11
d_ary_heap<4, int, std::less<int>> min_heap; // std::less -> smallest on top
Watch the direction: with the default std::greater the maximum is on top. Dijkstra's default traits use std::less (from shortest_path_semiring), which puts the minimum on top.
updatable_d_ary_heap¶
template <std::size_t D, typename Entry,
typename PriorityComparator = std::greater<Entry>,
typename IndicesMap = mapping_owning_view<std::unordered_map<Entry, std::size_t>>,
input_mapping<Entry> EntryPriorityMap = views::identity_map,
input_mapping<Entry> EntryIdMap = views::identity_map>
class updatable_d_ary_heap;
Adds contains(id), priority(id), promote(id, p) and demote(id, p), by tracking where each entry lives. The three extra parameters say how:
IndicesMapmaps an entry's identifier to its index in the heap array. The default is a hash map; for integral identifiers, pass astatic_mapand the lookup becomes an array access — this is exactly whatdijkstra_default_traitsdoes withvertex_map_t<Graph, std::size_t>.EntryPriorityMapextracts the priority from an entry.EntryIdMapextracts the identifier from an entry.
For a std::pair<vertex, distance> entry, the last two are views::element_map<1> and views::element_map<0>:
using entry = std::pair<unsigned int, double>;
updatable_d_ary_heap<2, entry, std::less<double>,
static_map<unsigned int, std::size_t>,
views::element_map<1>, // priority is entry.second
views::element_map<0>> // id is entry.first
heap(std::less<double>{}, static_map<unsigned int, std::size_t>(num_vertices));
heap.push({0u, 3.0});
heap.push({1u, 1.0});
heap.push({2u, 5.0});
heap.contains(1u); // true
heap.priority(1u); // 1.0
heap.promote(2u, 0.5); // now on top
promote asserts that the new priority is an improvement in the comparator's sense, and demote the opposite. With std::less — minimum on top — promoting means decreasing. Calling the wrong one is an assertion failure in a debug build and silent corruption in a release build.
disjoint_sets¶
template <typename K,
output_mapping<K> M = mapping_owning_view<std::unordered_map<K, unsigned int>>>
requires std::integral<mapped_value_t<M, K>>
class disjoint_sets;
Union-find with union-by-size and path halving, over arbitrary key types. The second parameter is how a key is mapped to its internal component index; as with the heap, the default is a hash map, and passing a static_map makes it an array access for integral keys.
disjoint_sets<int> sets;
for(int e : {11, 20, 3, 14}) sets.push(e);
sets.merge_keys(11, 20);
sets.find(11) == sets.find(20); // true
| Member | Effect |
|---|---|
push(k) |
adds k as a new singleton component |
find(k) |
the component index of k, compressing the path |
merge(c1, c2) |
merges two component indices, returns the survivor |
merge_keys(k1, k2) |
merge(find(k1), find(k2)) |
size(), empty(), clear() |
on the number of pushed elements |
Every key must be pushed before it is looked up. This is the structure kruskal runs on.
Other utilities¶
| Header | What it provides |
|---|---|
utility/semiring.hpp |
shortest_path_semiring, most_reliable_path_semiring, max_capacity_path_semiring, minimum_spanning_tree_semiring — see Shortest paths |
utility/rational.hpp |
rational<NumT, DenT> exact arithmetic, used by the geometric algorithms |
utility/geometry.hpp |
the cartesian_point, cartesian_segment and cartesian_line concepts and the cartesian traits |
utility/bounded_value.hpp |
integer types that widen automatically instead of narrowing |
utility/alias_method_sampler.hpp |
O(1) sampling from a discrete distribution |
utility/algorithmic_generator.hpp |
the algorithmic_generator concept and the range adaptor built on it |