diff --git a/include/graph/algorithm/dijkstra_shortest_paths.hpp b/include/graph/algorithm/dijkstra_shortest_paths.hpp index d0baa69..857aee0 100644 --- a/include/graph/algorithm/dijkstra_shortest_paths.hpp +++ b/include/graph/algorithm/dijkstra_shortest_paths.hpp @@ -120,42 +120,45 @@ using adj_list::index_vertex_range; // We use std::remove_reference_t in WF default types, invoke_result_t, and concept // constraints so that "const std::remove_reference_t&" always means a true const ref. // Default lambdas use "const auto&" instead of "const G&" to sidestep the issue entirely. -template (const std::remove_reference_t&, const edge_t&)>, - class Visitor = empty_visitor, - class Compare = less>, - class Combine = plus>> -requires vertex_property_map_for && // - (is_null_range_v || vertex_property_map_for) && // - convertible_to, vertex_id_t> && // - is_arithmetic_v> && // +template < + adjacency_list G, + input_range Sources, + class Distances, + class Predecessors, + class WF = function(const std::remove_reference_t&, const edge_t&)>, + class Visitor = empty_visitor, + class Compare = less>, + class Combine = plus>> +requires vertex_property_map_for && // + (is_null_range_v || vertex_property_map_for) && // + convertible_to, vertex_id_t> && // + is_arithmetic_v> && // basic_edge_weight_function, Compare, Combine> constexpr void dijkstra_shortest_paths( G&& g, const Sources& sources, Distances& distances, Predecessors& predecessor, - WF&& weight = [](const auto&, - const edge_t& uv) { return vertex_property_map_value_t(1); }, // default weight(g, uv) -> 1 - Visitor&& visitor = empty_visitor(), - Compare&& compare = less>(), - Combine&& combine = plus>()) { + WF&& weight = + [](const auto&, const edge_t& uv) { + return vertex_property_map_value_t(1); + }, // default weight(g, uv) -> 1 + Visitor&& visitor = empty_visitor(), + Compare&& compare = less>(), + Combine&& combine = plus>()) { using id_type = vertex_id_t; using distance_type = vertex_property_map_value_t; using weight_type = invoke_result_t&, edge_t>; // relaxing the target is the function of reducing the distance from the source to the target auto relax_target = [&g, &predecessor, &distances, &compare, &combine] // - (const edge_t& e, const vertex_id_t& uid, const weight_type& w_e) -> bool { - const id_type vid = target_id(g, e); + (const edge_t& uv, const vertex_id_t& uid, const weight_type& w) -> bool { + const id_type vid = target_id(g, uv); const distance_type d_u = distances[uid]; const distance_type d_v = distances[vid]; - if (compare(combine(d_u, w_e), d_v)) { - distances[vid] = combine(d_u, w_e); + if (compare(combine(d_u, w), d_v)) { + distances[vid] = combine(d_u, w); if constexpr (!is_null_range_v) { predecessor[vid] = uid; } @@ -182,60 +185,65 @@ constexpr void dijkstra_shortest_paths( constexpr auto zero = shortest_path_zero(); constexpr auto infinite = shortest_path_infinite_distance(); - // Priority queue stores vertex descriptors — lightweight 8-byte values (index or iterator), - // eliminating string copies for map-based graphs and enabling O(1) vertex_id extraction. - using vertex_desc = vertex_t>; - auto qcompare = [&g, &distances](vertex_desc a, vertex_desc b) { - return distances[vertex_id(g, a)] > distances[vertex_id(g, b)]; + struct weighted_vertex { + vertex_t> vertex_desc = {}; + distance_type weight = distance_type(); }; - using Queue = std::priority_queue, decltype(qcompare)>; + auto qcompare = [&compare](const weighted_vertex& a, const weighted_vertex& b) { + return compare(b.weight, a.weight); // min-heap: pop lowest weight first + }; + using Queue = std::priority_queue, decltype(qcompare)>; Queue queue(qcompare); // (The optimizer removes this loop if on_initialize_vertex() is empty.) - if constexpr (has_on_initialize_vertex) { - for (auto&& [uid, u] : views::vertexlist(g)) { - visitor.on_initialize_vertex(g, u); - } - } else if constexpr (has_on_initialize_vertex_id) { + if constexpr (has_on_initialize_vertex || has_on_initialize_vertex_id) { for (auto&& [uid, u] : views::vertexlist(g)) { - visitor.on_initialize_vertex(g, uid); + if constexpr (has_on_initialize_vertex) { + visitor.on_initialize_vertex(g, u); + } else if constexpr (has_on_initialize_vertex_id) { + visitor.on_initialize_vertex(g, uid); + } } } // Seed the queue with the initial vertice(s) - for (auto&& source : sources) { + for (auto&& uid : sources) { + vertex_t u; if constexpr (index_vertex_range>) { - if (source >= num_vertices(g)) { - throw std::out_of_range(std::format("dijkstra_shortest_paths: source vertex id '{}' is out of range", source)); + if (uid >= num_vertices(g)) { + throw std::out_of_range( + std::format("dijkstra_shortest_paths: uid vertex id '{}' is out of range", uid)); } + u = *find_vertex(g, uid); } else { - if (find_vertex(g, source) == std::ranges::end(vertices(g))) { - throw std::out_of_range(std::format("dijkstra_shortest_paths: source vertex id '{}' is out of range", source)); + auto v_it = find_vertex(g, uid); + if (v_it == std::ranges::end(vertices(g))) { + throw std::out_of_range( + std::format("dijkstra_shortest_paths: uid vertex id '{}' is out of range", uid)); } + u = *v_it; } - auto s_desc = *find_vertex(g, source); - queue.push(s_desc); - distances[source] = zero; // mark source as discovered + distances[uid] = zero; // mark uid as discovered + queue.push({u, zero}); if constexpr (has_on_discover_vertex) { - visitor.on_discover_vertex(g, s_desc); + visitor.on_discover_vertex(g, u); } else if constexpr (has_on_discover_vertex_id) { - visitor.on_discover_vertex(g, source); + visitor.on_discover_vertex(g, uid); } } // Main loop to process the queue while (!queue.empty()) { - auto u = queue.top(); + auto [u, w] = queue.top(); queue.pop(); - const id_type uid = vertex_id(g, u); // O(1) extraction from descriptor + const id_type uid = vertex_id(g, u); if constexpr (has_on_examine_vertex) { - visitor.on_examine_vertex(g, u); // descriptor already available, no find_vertex + visitor.on_examine_vertex(g, u); } else if constexpr (has_on_examine_vertex_id) { visitor.on_examine_vertex(g, uid); } // Process all outgoing edges from the current vertex - // Using descriptor directly avoids find_vertex for incidence view creation for (auto&& [vid, uv, w] : views::incidence(g, u, weight)) { if constexpr (has_on_examine_edge) { visitor.on_examine_edge(g, uv); @@ -258,14 +266,12 @@ constexpr void dijkstra_shortest_paths( if constexpr (has_on_edge_relaxed) { visitor.on_edge_relaxed(g, uv); } - // Convert target_id to descriptor once; serves both visitor and queue push - auto v_desc = *find_vertex(g, vid); if constexpr (has_on_discover_vertex) { - visitor.on_discover_vertex(g, v_desc); + visitor.on_discover_vertex(g, *find_vertex(g, vid)); } else if constexpr (has_on_discover_vertex_id) { visitor.on_discover_vertex(g, vid); } - queue.push(v_desc); // push descriptor (8 bytes), not vertex ID + queue.push({*find_vertex(g, vid), distances[vid]}); } else { // This is an indicator of a bug in the algorithm and should be investigated. throw std::logic_error( @@ -277,7 +283,7 @@ constexpr void dijkstra_shortest_paths( if constexpr (has_on_edge_relaxed) { visitor.on_edge_relaxed(g, uv); } - queue.push(*find_vertex(g, vid)); // re-enqueue descriptor to re-evaluate + queue.push({*find_vertex(g, vid), distances[vid]}); // re-enqueue with updated distance } else { if constexpr (has_on_edge_not_relaxed) { visitor.on_edge_not_relaxed(g, uv); @@ -290,8 +296,9 @@ constexpr void dijkstra_shortest_paths( // and another path to this vertex has a lower accumulated weight, we'll process it again. // A consequence is that examine_vertex could be called twice (or more) on the same vertex. if constexpr (has_on_finish_vertex) { - visitor.on_finish_vertex(g, u); // descriptor already available, no find_vertex - } else if constexpr (has_on_finish_vertex_id) { + visitor.on_finish_vertex(g, *find_vertex(g, uid)); + } + if constexpr (has_on_finish_vertex_id) { visitor.on_finish_vertex(g, uid); } } // while(!queue.empty()) @@ -306,27 +313,30 @@ constexpr void dijkstra_shortest_paths( * * @see dijkstra_shortest_paths(G&&, const Sources&, Distances&, Predecessors&, WF&&, Visitor&&, Compare&&, Combine&&) */ -template (const std::remove_reference_t&, const edge_t&)>, - class Visitor = empty_visitor, - class Compare = less>, - class Combine = plus>> -requires vertex_property_map_for && // - (is_null_range_v || vertex_property_map_for) && // - is_arithmetic_v> && // +template < + adjacency_list G, + class Distances, + class Predecessors, + class WF = function(const std::remove_reference_t&, const edge_t&)>, + class Visitor = empty_visitor, + class Compare = less>, + class Combine = plus>> +requires vertex_property_map_for && // + (is_null_range_v || vertex_property_map_for) && // + is_arithmetic_v> && // basic_edge_weight_function, Compare, Combine> constexpr void dijkstra_shortest_paths( - G&& g, - const vertex_id_t& source, - Distances& distances, - Predecessors& predecessor, - WF&& weight = [](const auto&, - const edge_t& uv) { return vertex_property_map_value_t(1); }, // default weight(g, uv) -> 1 - Visitor&& visitor = empty_visitor(), - Compare&& compare = less>(), - Combine&& combine = plus>()) { + G&& g, + const vertex_id_t& source, + Distances& distances, + Predecessors& predecessor, + WF&& weight = + [](const auto&, const edge_t& uv) { + return vertex_property_map_value_t(1); + }, // default weight(g, uv) -> 1 + Visitor&& visitor = empty_visitor(), + Compare&& compare = less>(), + Combine&& combine = plus>()) { dijkstra_shortest_paths(g, subrange(&source, (&source + 1)), distances, predecessor, weight, forward(visitor), forward(compare), forward(combine)); } @@ -362,26 +372,29 @@ constexpr void dijkstra_shortest_paths( * * @see dijkstra_shortest_paths() for full documentation and complexity analysis. */ -template (const std::remove_reference_t&, const edge_t&)>, - class Visitor = empty_visitor, - class Compare = less>, - class Combine = plus>> -requires vertex_property_map_for && // - convertible_to, vertex_id_t> && // - is_arithmetic_v> && // +template < + adjacency_list G, + input_range Sources, + class Distances, + class WF = function(const std::remove_reference_t&, const edge_t&)>, + class Visitor = empty_visitor, + class Compare = less>, + class Combine = plus>> +requires vertex_property_map_for && // + convertible_to, vertex_id_t> && // + is_arithmetic_v> && // basic_edge_weight_function, Compare, Combine> constexpr void dijkstra_shortest_distances( G&& g, const Sources& sources, Distances& distances, - WF&& weight = [](const auto&, - const edge_t& uv) { return vertex_property_map_value_t(1); }, // default weight(g, uv) -> 1 - Visitor&& visitor = empty_visitor(), - Compare&& compare = less>(), - Combine&& combine = plus>()) { + WF&& weight = + [](const auto&, const edge_t& uv) { + return vertex_property_map_value_t(1); + }, // default weight(g, uv) -> 1 + Visitor&& visitor = empty_visitor(), + Compare&& compare = less>(), + Combine&& combine = plus>()) { dijkstra_shortest_paths(g, sources, distances, _null_predecessors, forward(weight), forward(visitor), forward(compare), forward(combine)); } @@ -395,24 +408,27 @@ constexpr void dijkstra_shortest_distances( * * @see dijkstra_shortest_distances(G&&, const Sources&, Distances&, WF&&, Visitor&&, Compare&&, Combine&&) */ -template (const std::remove_reference_t&, const edge_t&)>, - class Visitor = empty_visitor, - class Compare = less>, - class Combine = plus>> -requires vertex_property_map_for && // - is_arithmetic_v> && // +template < + adjacency_list G, + class Distances, + class WF = function(const std::remove_reference_t&, const edge_t&)>, + class Visitor = empty_visitor, + class Compare = less>, + class Combine = plus>> +requires vertex_property_map_for && // + is_arithmetic_v> && // basic_edge_weight_function, Compare, Combine> constexpr void dijkstra_shortest_distances( - G&& g, - const vertex_id_t& source, - Distances& distances, - WF&& weight = [](const auto&, - const edge_t& uv) { return vertex_property_map_value_t(1); }, // default weight(g, uv) -> 1 - Visitor&& visitor = empty_visitor(), - Compare&& compare = less>(), - Combine&& combine = plus>()) { + G&& g, + const vertex_id_t& source, + Distances& distances, + WF&& weight = + [](const auto&, const edge_t& uv) { + return vertex_property_map_value_t(1); + }, // default weight(g, uv) -> 1 + Visitor&& visitor = empty_visitor(), + Compare&& compare = less>(), + Combine&& combine = plus>()) { dijkstra_shortest_paths(g, subrange(&source, (&source + 1)), distances, _null_predecessors, forward(weight), forward(visitor), forward(compare), forward(combine)); }