Skip to content

Commit bcfdb52

Browse files
committed
pfs fix + dijkstra alignment and docs + template docs alignment
1 parent c1b0bdc commit bcfdb52

12 files changed

Lines changed: 278 additions & 127 deletions

File tree

Doxyfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ TAB_SIZE = 4
289289
# with the commands \{ and \} for these it is advised to use the version @{ and
290290
# @} or use a double escape (\\{ and \\})
291291

292-
ALIASES =
292+
ALIASES = hideparams="@xmlonly <hideparams/> @endxmlonly"
293293

294294
# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources
295295
# only. Doxygen will then generate output that is more tailored for C. For

include/gl/algorithm/pathfinding/dijkstra.hpp

Lines changed: 133 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
// This file is part of the CPP-GL project (https://github.com/SpectraL519/cpp-gl).
33
// Licensed under the MIT License. See the LICENSE file in the project root for full license information.
44

5+
/// @file gl/algorithm/pathfinding/dijkstra.hpp
6+
/// @brief Concrete implementation of Dijkstra's single-source shortest path algorithm and path reconstruction utilities.
7+
58
#pragma once
69

710
#include "gl/algorithm/core.hpp"
@@ -10,30 +13,117 @@
1013
#include "gl/constants.hpp"
1114
#include "gl/types/core.hpp"
1215

13-
#include <deque>
14-
1516
namespace gl::algorithm {
1617

18+
/// @ingroup GL GL-Algorithm
19+
/// @brief A descriptor structure holding the results of a single-source shortest path execution.
20+
///
21+
/// ### Template Parameters
22+
/// | Parameter | Description | Constraint |
23+
/// | :-------- | :--- | :--- |
24+
/// | G | The type of the graph. | Must satisfy the [**c_graph**](gl_concepts.md#gl-traits-c-graph) concept. |
25+
/// | VertexDistanceType | The numeric type used to represent accumulated path weights/distances. | Must satisfy the [**c_arithmetic**](gl_concepts.md#gl-traits-c-arithmetic) concept. |
1726
template <traits::c_graph G, traits::c_arithmetic VertexDistanceType>
1827
struct paths_descriptor {
1928
using id_type = typename G::id_type;
2029
using distance_type = VertexDistanceType;
2130

31+
/// @brief Constructs a descriptor sized for the given number of vertices.
32+
/// @param n_vertices The total number of vertices in the graph.
2233
paths_descriptor(const size_type n_vertices)
2334
: predecessors(n_vertices, invalid_id), distances(n_vertices) {}
2435

36+
/// @brief The predecessor map tracking the optimal path tree.
2537
predecessors_map<G> predecessors;
38+
/// @brief The accumulated shortest distances to each vertex from the source.
2639
std::vector<distance_type> distances;
2740
};
2841

42+
/// @ingroup GL GL-Algorithm
43+
/// @brief An alias for @ref gl::algorithm::paths_descriptor "paths_descriptor" that automatically deduces the appropriate distance type for the graph.
44+
/// @tparam G The type of the graph.
2945
template <traits::c_graph G>
3046
using paths_descriptor_type = paths_descriptor<G, vertex_distance_type<G>>;
3147

48+
/// @ingroup GL GL-Algorithm
49+
/// @brief Factory function to create an initialized paths descriptor sized for the given graph.
50+
/// @tparam G The type of the graph.
51+
/// @param graph The graph to size the descriptor against.
52+
/// @return A @ref gl::algorithm::paths_descriptor "paths_descriptor" initialized with invalid predecessors and default-constructed distances.
3253
template <traits::c_graph G>
3354
[[nodiscard]] gl_attr_force_inline paths_descriptor_type<G> make_paths_descriptor(const G& graph) {
3455
return paths_descriptor_type<G>{graph.n_vertices()};
3556
}
3657

58+
/// @ingroup GL GL-Algorithm
59+
/// @brief Internal node structure for Dijkstra's algorithm to snapshot distances and preserve heap invariants.
60+
///
61+
/// This structure is used in the @ref gl::algorithm::dijkstra_shortest_paths "dijkstra_shortest_paths" algorithm
62+
/// to capture the state of a vertex at the moment it is enqueued, ensuring that the priority queue remains stable
63+
/// even if the global distance map is updated during traversal.
64+
///
65+
/// @tparam G The type of the graph. Must satisfy the [**c_graph**](gl_concepts.md#gl-traits-c-graph) concept.
66+
template <traits::c_graph G>
67+
struct dijkstra_search_node {
68+
/// @brief The type of the vertex ID.
69+
using id_type = typename G::id_type;
70+
71+
id_type vertex_id; ///< @brief The ID of the vertex represented by this node.
72+
id_type pred_id; ///< The ID of the predecessor vertex used to reach this node.
73+
vertex_distance_type<G>
74+
distance; ///< The accumulated distance from the source to this vertex at the time of enqueueing.
75+
};
76+
77+
/// @ingroup GL GL-Algorithm
78+
/// @brief Computes the shortest paths from a single source vertex to all reachable vertices using Dijkstra's algorithm.
79+
///
80+
/// This algorithm utilizes the generic @ref gl::algorithm::pfs "pfs" template using the dedicated
81+
/// @ref gl::algorithm::dijkstra_search_node "serch node type" to perform a priority-first search based
82+
/// on accumulated edge weights. It strictly requires non-negative edge weights; if a negative weight is
83+
/// encountered during traversal, the algorithm immediately throws an exception.
84+
///
85+
/// ### Example Usage
86+
/// ```cpp
87+
/// auto paths = gl::algorithm::dijkstra_shortest_paths(graph, source_id); // (1)!
88+
///
89+
/// auto path_to_target
90+
/// = gl::algorithm::reconstruct_path(paths.predecessors, target_id); // (2)!
91+
/// std::cout << "Path: "
92+
/// << gl::io::range_formatter(path_to_target, " -> ", "", "") // (3)!
93+
/// << "\nDistance: " << paths.distances[target_id] << '\n'; // (4)!
94+
/// ```
95+
///
96+
/// 1\. Executes the shortest path calculation from the given `source_id`.
97+
///
98+
/// 2\. Reconstructs the exact sequence of vertices from the source to the `target_id` using the @ref gl::algorithm::reconstruct_path "reconstruct_path" function.
99+
///
100+
/// 3\. Prints the path to the target vertex using the @ref gl::io::range_formatter "range_formatter" helper.
101+
///
102+
/// 4\. Retrievs the total distance to the target vertex from the @ref gl::algorithm::paths_descriptor "paths descriptor" object returned by Dijkstra's algorithm.
103+
///
104+
/// > [!INFO] Algorithmic Complexity
105+
/// >
106+
/// > The time complexity depends on the underlying representation of `GraphType` and the priority queue overhead:
107+
/// > - **Adjacency List Representations**: \f$O((|V| + |E|) \log |V|)\f$
108+
/// > - *Includes:* @ref gl::impl::list_t "list_t" and @ref gl::impl::flat_list_t "flat_list_t".
109+
/// > - **Dense Adjacency Matrix Representations**: \f$O(|V|^2 + |E| \log |V|)\f$
110+
/// > - *Includes:* @ref gl::impl::matrix_t "matrix_t" and @ref gl::impl::flat_matrix_t "flat_matrix_t".
111+
/// > - *Note:* Iterating over adjacent vertices requires scanning the entire \f$|V|\f$-length matrix row.
112+
///
113+
/// ### Template Parameters
114+
/// | Parameter | Description | Constraint |
115+
/// | :-------- | :--- | :--- |
116+
/// | G | The type of the graph being traversed. Must define a valid distance/weight property. | Must satisfy the [**c_graph**](gl_concepts.md#gl-traits-c-graph) concept. |
117+
/// | PreVisitCallback | Type of the callable executed immediately before a vertex is officially visited. | Must be one of:<br/>- `(id_type) -> void` callable<br/>- An @ref gl::algorithm::empty_callback "empty_callback" |
118+
/// | PostVisitCallback | Type of the callable executed after all adjacent edges of a vertex are evaluated. | Must be one of:<br/>- `(id_type) -> void` callable<br/>- An @ref gl::algorithm::empty_callback "empty_callback" |
119+
///
120+
/// @param graph The graph to evaluate.
121+
/// @param source_id The starting vertex ID for the shortest path calculation.
122+
/// @param pre_visit Hook executed immediately before the internal visit logic.
123+
/// @param post_visit Hook executed after all adjacent edges of the current vertex have been enqueued.
124+
/// @return A @ref gl::algorithm::paths_descriptor "paths_descriptor" containing the accumulated distances and predecessor map.
125+
/// @throws std::invalid_argument If an edge with a negative weight is encountered.
126+
/// @hideparams
37127
template <
38128
traits::c_graph G,
39129
traits::c_optional_callback<void, typename G::id_type> PreVisitCallback = empty_callback,
@@ -55,19 +145,27 @@ template <
55145

56146
std::optional<edge_type> negative_edge;
57147

148+
// Seed the queue with the custom snapshot node
149+
std::vector<dijkstra_search_node<G>> init_queue{
150+
{source_id, source_id, distance_type{}}
151+
};
152+
58153
pfs(
59154
graph,
60-
[&paths](const search_node<G>& lhs, const search_node<G>& rhs) {
61-
return paths.distances[lhs.vertex_id] > paths.distances[rhs.vertex_id];
155+
[](const dijkstra_search_node<G>& lhs, const dijkstra_search_node<G>& rhs
156+
) { // pq comparator
157+
return lhs.distance > rhs.distance;
158+
},
159+
init_queue,
160+
[&paths](const dijkstra_search_node<G>& node) { // visit_vertex_pred (stale node rejection)
161+
return node.distance <= paths.distances[to_idx(node.vertex_id)];
62162
},
63-
init_range<G>(source_id),
64-
empty_callback{}, // visit predicate
65163
empty_callback{}, // visit callback
66164
[&paths, &negative_edge](id_type vertex_id, const edge_type& in_edge)
67165
-> decision { // enqueue predicate
68166
const auto pred_id = in_edge.other(vertex_id);
69-
70167
const auto edge_weight = get_weight<G>(in_edge);
168+
71169
if (edge_weight < 0) {
72170
negative_edge.emplace(in_edge);
73171
return decision::abort;
@@ -85,6 +183,9 @@ template <
85183

86184
return false;
87185
},
186+
[&paths](id_type target_id, id_type pred_id, const edge_type&) { // make_node callback
187+
return dijkstra_search_node<G>{target_id, pred_id, paths.distances[to_idx(target_id)]};
188+
},
88189
pre_visit,
89190
post_visit
90191
);
@@ -102,26 +203,44 @@ template <
102203
return paths;
103204
}
104205

206+
/// @ingroup GL GL-Algorithm
207+
/// @brief Reconstructs the sequence of vertices forming a path to a specific target.
208+
///
209+
/// This utility walks backward through a predecessor map, starting from the `vertex_id`
210+
/// until it reaches the root vertex (a vertex that is its own predecessor).
211+
///
212+
/// ### Template Parameters
213+
/// | Parameter | Description | Constraint |
214+
/// | :-------- | :--- | :--- |
215+
/// | IdType | The type of the vertex IDs. | Must satisfy the [**c_id_type**](gl_concepts.md#gl-traits-c-id-type) concept. |
216+
/// | IdRange | The type of the random-access range containing the predecessor map. | Must satisfy the [**c_random_access_range_of**](gl_concepts.md#gl-traits-c-random-access-range-of) concept for `IdType`. |
217+
///
218+
/// @param predecessor_map The predecessor map generated by a traversal algorithm (e.g., BFS, DFS, Dijkstra).
219+
/// @param vertex_id The target vertex ID to reconstruct the path for.
220+
/// @return A `std::vector` containing the sequence of vertex IDs from the source to the target.
221+
/// @throws std::invalid_argument If the target `vertex_id` is unreachable (its predecessor is invalid).
222+
/// @hideparams
105223
template <traits::c_id_type IdType, traits::c_random_access_range_of<IdType> IdRange>
106-
[[nodiscard]] std::deque<IdType> reconstruct_path(
224+
[[nodiscard]] std::vector<IdType> reconstruct_path(
107225
const IdRange& predecessor_map, const IdType vertex_id
108226
) {
109227
if (not is_reachable(predecessor_map, vertex_id))
110228
throw std::invalid_argument(
111229
std::format("[alg::reconstruct_path] The given vertex is unreachable: {}", vertex_id)
112230
);
113231

114-
std::deque<IdType> path;
115-
IdType current_vertex = vertex_id;
232+
std::vector<IdType> path;
233+
auto curr = vertex_id;
116234

117235
while (true) {
118-
path.push_front(current_vertex);
119-
IdType predecessor = predecessor_map[to_idx(current_vertex)];
120-
if (predecessor == current_vertex)
236+
path.push_back(curr);
237+
auto pred = predecessor_map[to_idx(curr)];
238+
if (pred == curr)
121239
break;
122-
current_vertex = predecessor;
240+
curr = pred;
123241
}
124242

243+
std::ranges::reverse(path);
125244
path.shrink_to_fit();
126245
return path;
127246
}

include/gl/algorithm/templates/bfs.hpp

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ namespace gl::algorithm {
2929
///
3030
/// bool completed = gl::algorithm::bfs(
3131
/// graph,
32-
/// gl::algorithm::init_range<graph_type>(start_id), // (2)!
32+
/// gl::algorithm::init_range<graph_type>(start_id), // (2)!
3333
/// gl::algorithm::default_visit_vertex_predicate(visited), // (3)!
34-
/// [&](auto v, auto p) { // (4)!
34+
/// [&](auto v, auto p) { // (4)!
3535
/// std::cout << "Visited vertex " << v << '\n';
3636
/// return true; // Continue search
3737
/// },
@@ -50,15 +50,15 @@ namespace gl::algorithm {
5050
/// 5\. Predicate ensuring we only enqueue adjacent vertices that haven't been visited yet, returning a @ref gl::algorithm::decision "decision".
5151
///
5252
/// ### Template Parameters
53-
/// | Parameter | Description |
54-
/// | :-------- | :--- |
55-
/// | G | The type of the graph being traversed. |
56-
/// | InitQueueRangeType | The type of the container providing the initial roots to enqueue. |
57-
/// | VisitVertexPredicate | Type of the callable deciding if a popped vertex should be processed. |
58-
/// | VisitCallback | Type of the callable executed when a vertex is officially visited. |
59-
/// | EnqueueVertexPred | Type of the callable deciding if an adjacent vertex should be pushed to the queue. |
60-
/// | PreVisitCallback | Type of the callable executed immediately before `VisitCallback`. |
61-
/// | PostVisitCallback | Type of the callable executed after all adjacent edges are evaluated. |
53+
/// | Parameter | Description | Constraint |
54+
/// | :-------- | :--- | :--- |
55+
/// | G | The type of the graph being traversed. | Must satisfy the [**c_graph**](gl_concepts.md#gl-traits-c-graph) concept. |
56+
/// | InitQueueRangeType | The type of the container providing the initial roots to enqueue. | Must be a *forward range* of @ref gl::algorithm::search_node "search nodes". |
57+
/// | VisitVertexPredicate | Type of the callable deciding if a popped vertex should be processed. | Must be one of:<br/>- An `(id_type) -> bool` callable<br/>- An @ref gl::algorithm::empty_callback "empty_callback" |
58+
/// | VisitCallback | Type of the callable executed when a vertex is officially visited. | Must be one of:<br/>- An `(id_type, id_type) -> bool` callable<br/>- An @ref gl::algorithm::empty_callback "empty_callback" |
59+
/// | EnqueueVertexPred | Type of the callable deciding if an adjacent vertex should be pushed to the queue. | Must be one of:<br/>- An `(id_type, const edge_type&) -> decision` callable<br/>- An @ref gl::algorithm::empty_callback "empty_callback" |
60+
/// | PreVisitCallback | Type of the callable executed immediately before `VisitCallback`. | Must be one of:<br/>- An `(id_type) -> void` callable<br/>- An @ref gl::algorithm::empty_callback "empty_callback" |
61+
/// | PostVisitCallback | Type of the callable executed after all adjacent edges are evaluated. | Must be one of:<br/>- An `(id_type) -> void` callable<br/>- An @ref gl::algorithm::empty_callback "empty_callback" |
6262
///
6363
/// @param graph The graph to traverse.
6464
/// @param initial_queue_content A range of initial @ref gl::algorithm::search_node "search nodes" to seed the BFS queue.
@@ -71,6 +71,7 @@ namespace gl::algorithm {
7171
/// @param pre_visit Hook executed immediately before the `visit` callback.
7272
/// @param post_visit Hook executed after all adjacent edges of the current vertex have been evaluated.
7373
/// @return `true` if the queue was exhausted naturally, `false` if the search was aborted early by a callback or predicate.
74+
/// @hideparams
7475
template <
7576
traits::c_graph G,
7677
traits::c_forward_range_of<search_node<G>> InitQueueRangeType = std::vector<search_node<G>>,

0 commit comments

Comments
 (0)