Skip to content

Commit e2e0f11

Browse files
authored
Use weighted_vertex struct (descriptor + distance) in dijkstra_shortest_paths queue (#13)
Replace the vertex_desc-based priority queue with a weighted_vertex struct that stores both a vertex descriptor and its current distance, mirroring the approach used in dijkstra_clrs. The comparator now compares stored weights directly instead of looking up distances[vertex_id(g, v)] on every comparison.
1 parent 6b64d19 commit e2e0f11

1 file changed

Lines changed: 120 additions & 104 deletions

File tree

include/graph/algorithm/dijkstra_shortest_paths.hpp

Lines changed: 120 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -120,42 +120,45 @@ using adj_list::index_vertex_range;
120120
// We use std::remove_reference_t<G> in WF default types, invoke_result_t, and concept
121121
// constraints so that "const std::remove_reference_t<G>&" always means a true const ref.
122122
// Default lambdas use "const auto&" instead of "const G&" to sidestep the issue entirely.
123-
template <adjacency_list G,
124-
input_range Sources,
125-
class Distances,
126-
class Predecessors,
127-
class WF = function<vertex_property_map_value_t<Distances>(const std::remove_reference_t<G>&, const edge_t<G>&)>,
128-
class Visitor = empty_visitor,
129-
class Compare = less<vertex_property_map_value_t<Distances>>,
130-
class Combine = plus<vertex_property_map_value_t<Distances>>>
131-
requires vertex_property_map_for<Distances, G> && //
132-
(is_null_range_v<Predecessors> || vertex_property_map_for<Predecessors, G>) && //
133-
convertible_to<range_value_t<Sources>, vertex_id_t<G>> && //
134-
is_arithmetic_v<vertex_property_map_value_t<Distances>> && //
123+
template <
124+
adjacency_list G,
125+
input_range Sources,
126+
class Distances,
127+
class Predecessors,
128+
class WF = function<vertex_property_map_value_t<Distances>(const std::remove_reference_t<G>&, const edge_t<G>&)>,
129+
class Visitor = empty_visitor,
130+
class Compare = less<vertex_property_map_value_t<Distances>>,
131+
class Combine = plus<vertex_property_map_value_t<Distances>>>
132+
requires vertex_property_map_for<Distances, G> && //
133+
(is_null_range_v<Predecessors> || vertex_property_map_for<Predecessors, G>) && //
134+
convertible_to<range_value_t<Sources>, vertex_id_t<G>> && //
135+
is_arithmetic_v<vertex_property_map_value_t<Distances>> && //
135136
basic_edge_weight_function<G, WF, vertex_property_map_value_t<Distances>, Compare, Combine>
136137
constexpr void dijkstra_shortest_paths(
137138
G&& g,
138139
const Sources& sources,
139140
Distances& distances,
140141
Predecessors& predecessor,
141-
WF&& weight = [](const auto&,
142-
const edge_t<G>& uv) { return vertex_property_map_value_t<Distances>(1); }, // default weight(g, uv) -> 1
143-
Visitor&& visitor = empty_visitor(),
144-
Compare&& compare = less<vertex_property_map_value_t<Distances>>(),
145-
Combine&& combine = plus<vertex_property_map_value_t<Distances>>()) {
142+
WF&& weight =
143+
[](const auto&, const edge_t<G>& uv) {
144+
return vertex_property_map_value_t<Distances>(1);
145+
}, // default weight(g, uv) -> 1
146+
Visitor&& visitor = empty_visitor(),
147+
Compare&& compare = less<vertex_property_map_value_t<Distances>>(),
148+
Combine&& combine = plus<vertex_property_map_value_t<Distances>>()) {
146149
using id_type = vertex_id_t<G>;
147150
using distance_type = vertex_property_map_value_t<Distances>;
148151
using weight_type = invoke_result_t<WF, const std::remove_reference_t<G>&, edge_t<G>>;
149152

150153
// relaxing the target is the function of reducing the distance from the source to the target
151154
auto relax_target = [&g, &predecessor, &distances, &compare, &combine] //
152-
(const edge_t<G>& e, const vertex_id_t<G>& uid, const weight_type& w_e) -> bool {
153-
const id_type vid = target_id(g, e);
155+
(const edge_t<G>& uv, const vertex_id_t<G>& uid, const weight_type& w) -> bool {
156+
const id_type vid = target_id(g, uv);
154157
const distance_type d_u = distances[uid];
155158
const distance_type d_v = distances[vid];
156159

157-
if (compare(combine(d_u, w_e), d_v)) {
158-
distances[vid] = combine(d_u, w_e);
160+
if (compare(combine(d_u, w), d_v)) {
161+
distances[vid] = combine(d_u, w);
159162
if constexpr (!is_null_range_v<Predecessors>) {
160163
predecessor[vid] = uid;
161164
}
@@ -182,60 +185,65 @@ constexpr void dijkstra_shortest_paths(
182185
constexpr auto zero = shortest_path_zero<distance_type>();
183186
constexpr auto infinite = shortest_path_infinite_distance<distance_type>();
184187

185-
// Priority queue stores vertex descriptors — lightweight 8-byte values (index or iterator),
186-
// eliminating string copies for map-based graphs and enabling O(1) vertex_id extraction.
187-
using vertex_desc = vertex_t<std::remove_reference_t<G>>;
188-
auto qcompare = [&g, &distances](vertex_desc a, vertex_desc b) {
189-
return distances[vertex_id(g, a)] > distances[vertex_id(g, b)];
188+
struct weighted_vertex {
189+
vertex_t<std::remove_reference_t<G>> vertex_desc = {};
190+
distance_type weight = distance_type();
190191
};
191-
using Queue = std::priority_queue<vertex_desc, std::vector<vertex_desc>, decltype(qcompare)>;
192+
auto qcompare = [&compare](const weighted_vertex& a, const weighted_vertex& b) {
193+
return compare(b.weight, a.weight); // min-heap: pop lowest weight first
194+
};
195+
using Queue = std::priority_queue<weighted_vertex, std::vector<weighted_vertex>, decltype(qcompare)>;
192196
Queue queue(qcompare);
193197

194198
// (The optimizer removes this loop if on_initialize_vertex() is empty.)
195-
if constexpr (has_on_initialize_vertex<G, Visitor>) {
196-
for (auto&& [uid, u] : views::vertexlist(g)) {
197-
visitor.on_initialize_vertex(g, u);
198-
}
199-
} else if constexpr (has_on_initialize_vertex_id<G, Visitor>) {
199+
if constexpr (has_on_initialize_vertex<G, Visitor> || has_on_initialize_vertex_id<G, Visitor>) {
200200
for (auto&& [uid, u] : views::vertexlist(g)) {
201-
visitor.on_initialize_vertex(g, uid);
201+
if constexpr (has_on_initialize_vertex<G, Visitor>) {
202+
visitor.on_initialize_vertex(g, u);
203+
} else if constexpr (has_on_initialize_vertex_id<G, Visitor>) {
204+
visitor.on_initialize_vertex(g, uid);
205+
}
202206
}
203207
}
204208

205209
// Seed the queue with the initial vertice(s)
206-
for (auto&& source : sources) {
210+
for (auto&& uid : sources) {
211+
vertex_t<G> u;
207212
if constexpr (index_vertex_range<std::remove_reference_t<G>>) {
208-
if (source >= num_vertices(g)) {
209-
throw std::out_of_range(std::format("dijkstra_shortest_paths: source vertex id '{}' is out of range", source));
213+
if (uid >= num_vertices(g)) {
214+
throw std::out_of_range(
215+
std::format("dijkstra_shortest_paths: uid vertex id '{}' is out of range", uid));
210216
}
217+
u = *find_vertex(g, uid);
211218
} else {
212-
if (find_vertex(g, source) == std::ranges::end(vertices(g))) {
213-
throw std::out_of_range(std::format("dijkstra_shortest_paths: source vertex id '{}' is out of range", source));
219+
auto v_it = find_vertex(g, uid);
220+
if (v_it == std::ranges::end(vertices(g))) {
221+
throw std::out_of_range(
222+
std::format("dijkstra_shortest_paths: uid vertex id '{}' is out of range", uid));
214223
}
224+
u = *v_it;
215225
}
216-
auto s_desc = *find_vertex(g, source);
217-
queue.push(s_desc);
218-
distances[source] = zero; // mark source as discovered
226+
distances[uid] = zero; // mark uid as discovered
227+
queue.push({u, zero});
219228
if constexpr (has_on_discover_vertex<G, Visitor>) {
220-
visitor.on_discover_vertex(g, s_desc);
229+
visitor.on_discover_vertex(g, u);
221230
} else if constexpr (has_on_discover_vertex_id<G, Visitor>) {
222-
visitor.on_discover_vertex(g, source);
231+
visitor.on_discover_vertex(g, uid);
223232
}
224233
}
225234

226235
// Main loop to process the queue
227236
while (!queue.empty()) {
228-
auto u = queue.top();
237+
auto [u, w] = queue.top();
229238
queue.pop();
230-
const id_type uid = vertex_id(g, u); // O(1) extraction from descriptor
239+
const id_type uid = vertex_id(g, u);
231240
if constexpr (has_on_examine_vertex<G, Visitor>) {
232-
visitor.on_examine_vertex(g, u); // descriptor already available, no find_vertex
241+
visitor.on_examine_vertex(g, u);
233242
} else if constexpr (has_on_examine_vertex_id<G, Visitor>) {
234243
visitor.on_examine_vertex(g, uid);
235244
}
236245

237246
// Process all outgoing edges from the current vertex
238-
// Using descriptor directly avoids find_vertex for incidence view creation
239247
for (auto&& [vid, uv, w] : views::incidence(g, u, weight)) {
240248
if constexpr (has_on_examine_edge<G, Visitor>) {
241249
visitor.on_examine_edge(g, uv);
@@ -258,14 +266,12 @@ constexpr void dijkstra_shortest_paths(
258266
if constexpr (has_on_edge_relaxed<G, Visitor>) {
259267
visitor.on_edge_relaxed(g, uv);
260268
}
261-
// Convert target_id to descriptor once; serves both visitor and queue push
262-
auto v_desc = *find_vertex(g, vid);
263269
if constexpr (has_on_discover_vertex<G, Visitor>) {
264-
visitor.on_discover_vertex(g, v_desc);
270+
visitor.on_discover_vertex(g, *find_vertex(g, vid));
265271
} else if constexpr (has_on_discover_vertex_id<G, Visitor>) {
266272
visitor.on_discover_vertex(g, vid);
267273
}
268-
queue.push(v_desc); // push descriptor (8 bytes), not vertex ID
274+
queue.push({*find_vertex(g, vid), distances[vid]});
269275
} else {
270276
// This is an indicator of a bug in the algorithm and should be investigated.
271277
throw std::logic_error(
@@ -277,7 +283,7 @@ constexpr void dijkstra_shortest_paths(
277283
if constexpr (has_on_edge_relaxed<G, Visitor>) {
278284
visitor.on_edge_relaxed(g, uv);
279285
}
280-
queue.push(*find_vertex(g, vid)); // re-enqueue descriptor to re-evaluate
286+
queue.push({*find_vertex(g, vid), distances[vid]}); // re-enqueue with updated distance
281287
} else {
282288
if constexpr (has_on_edge_not_relaxed<G, Visitor>) {
283289
visitor.on_edge_not_relaxed(g, uv);
@@ -290,8 +296,9 @@ constexpr void dijkstra_shortest_paths(
290296
// and another path to this vertex has a lower accumulated weight, we'll process it again.
291297
// A consequence is that examine_vertex could be called twice (or more) on the same vertex.
292298
if constexpr (has_on_finish_vertex<G, Visitor>) {
293-
visitor.on_finish_vertex(g, u); // descriptor already available, no find_vertex
294-
} else if constexpr (has_on_finish_vertex_id<G, Visitor>) {
299+
visitor.on_finish_vertex(g, *find_vertex(g, uid));
300+
}
301+
if constexpr (has_on_finish_vertex_id<G, Visitor>) {
295302
visitor.on_finish_vertex(g, uid);
296303
}
297304
} // while(!queue.empty())
@@ -306,27 +313,30 @@ constexpr void dijkstra_shortest_paths(
306313
*
307314
* @see dijkstra_shortest_paths(G&&, const Sources&, Distances&, Predecessors&, WF&&, Visitor&&, Compare&&, Combine&&)
308315
*/
309-
template <adjacency_list G,
310-
class Distances,
311-
class Predecessors,
312-
class WF = function<vertex_property_map_value_t<Distances>(const std::remove_reference_t<G>&, const edge_t<G>&)>,
313-
class Visitor = empty_visitor,
314-
class Compare = less<vertex_property_map_value_t<Distances>>,
315-
class Combine = plus<vertex_property_map_value_t<Distances>>>
316-
requires vertex_property_map_for<Distances, G> && //
317-
(is_null_range_v<Predecessors> || vertex_property_map_for<Predecessors, G>) && //
318-
is_arithmetic_v<vertex_property_map_value_t<Distances>> && //
316+
template <
317+
adjacency_list G,
318+
class Distances,
319+
class Predecessors,
320+
class WF = function<vertex_property_map_value_t<Distances>(const std::remove_reference_t<G>&, const edge_t<G>&)>,
321+
class Visitor = empty_visitor,
322+
class Compare = less<vertex_property_map_value_t<Distances>>,
323+
class Combine = plus<vertex_property_map_value_t<Distances>>>
324+
requires vertex_property_map_for<Distances, G> && //
325+
(is_null_range_v<Predecessors> || vertex_property_map_for<Predecessors, G>) && //
326+
is_arithmetic_v<vertex_property_map_value_t<Distances>> && //
319327
basic_edge_weight_function<G, WF, vertex_property_map_value_t<Distances>, Compare, Combine>
320328
constexpr void dijkstra_shortest_paths(
321-
G&& g,
322-
const vertex_id_t<G>& source,
323-
Distances& distances,
324-
Predecessors& predecessor,
325-
WF&& weight = [](const auto&,
326-
const edge_t<G>& uv) { return vertex_property_map_value_t<Distances>(1); }, // default weight(g, uv) -> 1
327-
Visitor&& visitor = empty_visitor(),
328-
Compare&& compare = less<vertex_property_map_value_t<Distances>>(),
329-
Combine&& combine = plus<vertex_property_map_value_t<Distances>>()) {
329+
G&& g,
330+
const vertex_id_t<G>& source,
331+
Distances& distances,
332+
Predecessors& predecessor,
333+
WF&& weight =
334+
[](const auto&, const edge_t<G>& uv) {
335+
return vertex_property_map_value_t<Distances>(1);
336+
}, // default weight(g, uv) -> 1
337+
Visitor&& visitor = empty_visitor(),
338+
Compare&& compare = less<vertex_property_map_value_t<Distances>>(),
339+
Combine&& combine = plus<vertex_property_map_value_t<Distances>>()) {
330340
dijkstra_shortest_paths(g, subrange(&source, (&source + 1)), distances, predecessor, weight,
331341
forward<Visitor>(visitor), forward<Compare>(compare), forward<Combine>(combine));
332342
}
@@ -362,26 +372,29 @@ constexpr void dijkstra_shortest_paths(
362372
*
363373
* @see dijkstra_shortest_paths() for full documentation and complexity analysis.
364374
*/
365-
template <adjacency_list G,
366-
input_range Sources,
367-
class Distances,
368-
class WF = function<vertex_property_map_value_t<Distances>(const std::remove_reference_t<G>&, const edge_t<G>&)>,
369-
class Visitor = empty_visitor,
370-
class Compare = less<vertex_property_map_value_t<Distances>>,
371-
class Combine = plus<vertex_property_map_value_t<Distances>>>
372-
requires vertex_property_map_for<Distances, G> && //
373-
convertible_to<range_value_t<Sources>, vertex_id_t<G>> && //
374-
is_arithmetic_v<vertex_property_map_value_t<Distances>> && //
375+
template <
376+
adjacency_list G,
377+
input_range Sources,
378+
class Distances,
379+
class WF = function<vertex_property_map_value_t<Distances>(const std::remove_reference_t<G>&, const edge_t<G>&)>,
380+
class Visitor = empty_visitor,
381+
class Compare = less<vertex_property_map_value_t<Distances>>,
382+
class Combine = plus<vertex_property_map_value_t<Distances>>>
383+
requires vertex_property_map_for<Distances, G> && //
384+
convertible_to<range_value_t<Sources>, vertex_id_t<G>> && //
385+
is_arithmetic_v<vertex_property_map_value_t<Distances>> && //
375386
basic_edge_weight_function<G, WF, vertex_property_map_value_t<Distances>, Compare, Combine>
376387
constexpr void dijkstra_shortest_distances(
377388
G&& g,
378389
const Sources& sources,
379390
Distances& distances,
380-
WF&& weight = [](const auto&,
381-
const edge_t<G>& uv) { return vertex_property_map_value_t<Distances>(1); }, // default weight(g, uv) -> 1
382-
Visitor&& visitor = empty_visitor(),
383-
Compare&& compare = less<vertex_property_map_value_t<Distances>>(),
384-
Combine&& combine = plus<vertex_property_map_value_t<Distances>>()) {
391+
WF&& weight =
392+
[](const auto&, const edge_t<G>& uv) {
393+
return vertex_property_map_value_t<Distances>(1);
394+
}, // default weight(g, uv) -> 1
395+
Visitor&& visitor = empty_visitor(),
396+
Compare&& compare = less<vertex_property_map_value_t<Distances>>(),
397+
Combine&& combine = plus<vertex_property_map_value_t<Distances>>()) {
385398
dijkstra_shortest_paths(g, sources, distances, _null_predecessors, forward<WF>(weight), forward<Visitor>(visitor),
386399
forward<Compare>(compare), forward<Combine>(combine));
387400
}
@@ -395,24 +408,27 @@ constexpr void dijkstra_shortest_distances(
395408
*
396409
* @see dijkstra_shortest_distances(G&&, const Sources&, Distances&, WF&&, Visitor&&, Compare&&, Combine&&)
397410
*/
398-
template <adjacency_list G,
399-
class Distances,
400-
class WF = function<vertex_property_map_value_t<Distances>(const std::remove_reference_t<G>&, const edge_t<G>&)>,
401-
class Visitor = empty_visitor,
402-
class Compare = less<vertex_property_map_value_t<Distances>>,
403-
class Combine = plus<vertex_property_map_value_t<Distances>>>
404-
requires vertex_property_map_for<Distances, G> && //
405-
is_arithmetic_v<vertex_property_map_value_t<Distances>> && //
411+
template <
412+
adjacency_list G,
413+
class Distances,
414+
class WF = function<vertex_property_map_value_t<Distances>(const std::remove_reference_t<G>&, const edge_t<G>&)>,
415+
class Visitor = empty_visitor,
416+
class Compare = less<vertex_property_map_value_t<Distances>>,
417+
class Combine = plus<vertex_property_map_value_t<Distances>>>
418+
requires vertex_property_map_for<Distances, G> && //
419+
is_arithmetic_v<vertex_property_map_value_t<Distances>> && //
406420
basic_edge_weight_function<G, WF, vertex_property_map_value_t<Distances>, Compare, Combine>
407421
constexpr void dijkstra_shortest_distances(
408-
G&& g,
409-
const vertex_id_t<G>& source,
410-
Distances& distances,
411-
WF&& weight = [](const auto&,
412-
const edge_t<G>& uv) { return vertex_property_map_value_t<Distances>(1); }, // default weight(g, uv) -> 1
413-
Visitor&& visitor = empty_visitor(),
414-
Compare&& compare = less<vertex_property_map_value_t<Distances>>(),
415-
Combine&& combine = plus<vertex_property_map_value_t<Distances>>()) {
422+
G&& g,
423+
const vertex_id_t<G>& source,
424+
Distances& distances,
425+
WF&& weight =
426+
[](const auto&, const edge_t<G>& uv) {
427+
return vertex_property_map_value_t<Distances>(1);
428+
}, // default weight(g, uv) -> 1
429+
Visitor&& visitor = empty_visitor(),
430+
Compare&& compare = less<vertex_property_map_value_t<Distances>>(),
431+
Combine&& combine = plus<vertex_property_map_value_t<Distances>>()) {
416432
dijkstra_shortest_paths(g, subrange(&source, (&source + 1)), distances, _null_predecessors, forward<WF>(weight),
417433
forward<Visitor>(visitor), forward<Compare>(compare), forward<Combine>(combine));
418434
}

0 commit comments

Comments
 (0)