Skip to content

Commit 927e652

Browse files
committed
alg templates doc refinement
1 parent 3bf4d5e commit 927e652

2 files changed

Lines changed: 59 additions & 40 deletions

File tree

docs/gl/algorithms/overview.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,11 @@ This separation ensures maximum code reuse, provides strict zero-cost abstractio
1111
Located at the lowest level, the generic templates define the strict structural execution of a search. They know nothing about *shortest paths*, *spanning trees*, or *cycle detection*. Their only responsibility is to manage the pending elements (a queue, stack, or priority queue) and invoke a series of user-provided callback hooks at specific moments during the traversal.
1212

1313
The core generic templates include:
14-
- [**`bfs`**](templates.md#breadth-first-search-bfs): A queue-based Breadth-First Search engine.
15-
- [**`dfs`**](templates.md#depth-first-search-dfs): A stack-based, iterative Depth-First Search engine.
16-
- [**`r_dfs`**](templates.md#recursive-depth-first-search-r_dfs): A recursive Depth-First Search engine utilizing the call stack.
17-
- [**`pfs`**](templates.md#priority-first-search-pfs): A priority-queue-based Priority-First Search engine for custom heuristics.
14+
15+
- [**`bfs`**](templates.md#the-core-engines): A queue-based Breadth-First Search engine.
16+
- [**`dfs`**](templates.md#the-core-engines): A stack-based, iterative Depth-First Search engine.
17+
- [**`r_dfs`**](templates.md#the-core-engines): A recursive Depth-First Search engine utilizing the call stack.
18+
- [**`pfs`**](templates.md#the-core-engines): A priority-queue-based Priority-First Search engine for custom heuristics.
1819

1920
### 2. The Concrete Algorithms
2021

docs/gl/algorithms/templates.md

Lines changed: 54 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@ All engines operate on a similar fundamental principle: pop a node from a fronti
88

99
The library provides four primary traversal engines.
1010

11-
* [**`bfs` (Breadth-First Search)**](../../cpp-gl/group__GL-Algorithm.md#function-bfs): Uses a `std::queue`. Explores the graph level by level, expanding uniformly outward from the initial range.
12-
* [**`dfs` (Depth-First Search)**](../../cpp-gl/group__GL-Algorithm.md#function-dfs): Uses a `std::stack`. Plunges as deeply as possible along a branch before backtracking.
13-
* [**`r_dfs` (Recursive DFS)**](../../cpp-gl/group__GL-Algorithm.md#function-r_dfs): Uses the C++ call stack. Instead of an initial range, it is initiated with a specific `start_id`. It operates identically to `dfs` but requires external logic to manage abort signals, as returning from the recursion only unwinds one level.
14-
* [**`pfs` (Priority-First Search)**](../../cpp-gl/group__GL-Algorithm.md#function-pfs): Uses a `std::priority_queue`. Requires a custom comparator (`PQCmp`) to mathematically order the frontier. This is the underlying engine for algorithms like Dijkstra and Prim.
11+
- [**`bfs` (Breadth-First Search)**](../../cpp-gl/group__GL-Algorithm.md#function-bfs): Uses a `std::queue`. Explores the graph level by level, expanding uniformly outward from the initial range.
12+
- [**`dfs` (Depth-First Search)**](../../cpp-gl/group__GL-Algorithm.md#function-dfs): Uses a `std::stack`. Dives as deeply as possible along a branch before backtracking.
13+
- [**`r_dfs` (Recursive DFS)**](../../cpp-gl/group__GL-Algorithm.md#function-r_dfs): Uses the C++ call stack. Instead of an initial range, it is initiated with a specific starting vertex ID. It operates identically to `dfs` but requires external logic to manage abort signals, as returning from the recursion only unwinds one level.
14+
- [**`pfs` (Priority-First Search)**](../../cpp-gl/group__GL-Algorithm.md#function-pfs): Uses a `std::priority_queue`. Requires a custom comparator (`PQCmp`) to mathematically order the frontier. This is the underlying engine for algorithms like Dijkstra and Prim.
1515

1616
## The Callback Sequence
1717

18-
The true power of the generic templates lies in their callback hooks. Every iteration of the engine loop rigidly follows a defined sequence. By injecting custom lambdas (or omitting them via `empty_callback`), you dictate the algorithm's behavior.
18+
The true power of the generic templates lies in their callback/predicate hooks. Every iteration of the engine loop rigidly follows a defined sequence. By injecting custom lambdas (or omitting them via the [**empty_callback**](../../cpp-gl/structgl_1_1algorithm_1_1empty__callback.md)), you dictate the algorithm's behavior.
1919

2020
### Execution Flowchart
2121

@@ -31,62 +31,80 @@ For a single popped node in `bfs`, `dfs`, or `pfs`, the execution flow looks exa
3131
The primary callback. If this returns `false`, the entire search is immediately aborted.
3232

3333
4. **Edge Iteration**
34-
The engine iterates over every outgoing edge connected to the `vertex_id`. For each edge:
34+
The engine iterates over every outgoing edge connected to the `vertex_id`. For each edge it calls:
3535

36-
* **`enqueue_vertex_pred(target_id, edge)`**
37-
Evaluates the target vertex. Returns a `gl::algorithm::decision`:
38-
* `abort`: Kills the entire algorithm.
39-
* `reject`: Ignores this edge and moves to the next.
40-
* `accept`: Approves the target for enqueueing.
36+
- **`enqueue_node_pred(target_id, edge)`**
4137

42-
* **`make_node(target_id, vertex_id, edge)`** *(PFS Only)*
43-
If the target was accepted, this hook allows you to construct a custom object to push into the search frontier.
38+
Evaluates whether a new search node should be created for the target. Returns a [**decision**](../../cpp-gl/structgl_1_1algorithm_1_1decision.md):
39+
40+
- `abort`: Kills the entire algorithm.
41+
- `reject`: Ignores this edge and moves to the next.
42+
- `accept`: Approves the target for enqueueing.
43+
44+
- **`make_node(target_id, vertex_id, edge)`** *(PFS Only)*
45+
46+
If the target was accepted, this hook allows you to construct a custom object to push into the search frontier.
4447

4548
5. **`post_visit(vertex_id)`**
4649
Executed after all adjacent edges have been evaluated and processed.
4750

48-
## Custom Node Injection (`pfs`)
51+
## Custom Node Injection (PFS)
4952

50-
While `bfs` and `dfs` strictly operate on the lightweight `gl::algorithm::search_node`, the `pfs` (Priority-First Search) engine often requires tracking dynamic state alongside the vertex ID.
53+
While BFS and DFS templates strictly operate on the lightweight [**gl::algorithm::search_node<G>**](../../cpp-gl/structgl_1_1algorithm_1_1search__node.md), the Priority-First Search template often requires tracking dynamic state alongside the vertex ID.
5154

5255
For instance, in Dijkstra's algorithm, the priority queue must sort nodes based on their accumulated distance from the starting point. You cannot sort based purely on the vertex ID.
5356

54-
`pfs` solves this by automatically inferring the `NodeType` from the initial queue range container. If your `NodeType` requires more than just `(target_id, pred_id)` to construct, you must provide a `MakeNodeCallback`.
57+
`pfs` solves this by automatically inferring the `NodeType` from the initial queue range container. If your `NodeType` requires more than just `(target_id, pred_id)` to construct, you must provide `MakeNodeCallback` which is a `(vertex_id, pred_id, edge) -> NodeType` callback.
5558

5659
### Example: PFS Stateful Nodes
5760

5861
```cpp
59-
// 1. Define a custom stateful node
60-
struct path_node {
62+
struct path_node { // (1)!
6163
gl::default_id_type vertex_id;
6264
gl::default_id_type pred_id;
6365
int accumulated_distance;
6466
};
6567

66-
// 2. Define the priority comparator
67-
auto cmp = [](const path_node& a, const path_node& b) {
68-
return a.accumulated_distance > b.accumulated_distance; // Min-Heap
68+
std::vector<int> distance_map( // (2)!
69+
graph.n_vertices(), std::numeric_limits<int>::max()
70+
);
71+
distance_map[start_id] = 0;
72+
73+
auto cmp = [](const path_node& a, const path_node& b) { // (3)!
74+
return a.accumulated_distance > b.accumulated_distance;
6975
};
7076

71-
// 3. Setup the initial range
72-
std::vector<path_node> init_range = { path_node{start_id, start_id, 0} };
77+
std::vector<path_node> init_nodes = { // (4)!
78+
path_node{start_id, start_id, 0}
79+
};
7380

74-
// 4. Run the PFS engine
75-
gl::algorithm::pfs(
81+
gl::algorithm::pfs( // (5)!
7682
graph,
7783
cmp,
78-
init_range,
79-
gl::algorithm::empty_callback{}, // visit_vertex_pred
80-
gl::algorithm::empty_callback{}, // visit
81-
82-
// enqueue_vertex_pred
83-
[&](auto target_id, const auto& edge) {
84-
return distance_map[target_id] > current_dist + edge.properties().weight;
84+
init_nodes,
85+
gl::algorithm::empty_callback{}, // (6)!
86+
gl::algorithm::empty_callback{},
87+
[&](auto target_id, const auto& edge) { // (7)!
88+
return distance_map[target_id] > distance_map[edge.source()] + edge.properties().weight;
8589
},
86-
87-
// make_node - Construct the stateful object for the queue
88-
[&](auto target_id, auto source_id, const auto& edge) {
89-
int new_dist = current_dist + edge.properties().weight;
90+
[&](auto target_id, auto source_id, const auto& edge) { // (8)!
91+
int new_dist = distance_map[source_id] + edge.properties().weight;
92+
distance_map[target_id] = new_dist; // (9)!
9093
return path_node{target_id, source_id, new_dist};
9194
}
9295
);
96+
```
97+
98+
1. Define a custom stateful node tracking the distance accumulated so far.
99+
2. Initialize a global distance map with "infinity", setting the start vertex distance to 0.
100+
3. Define the priority comparator for a distance-based Min-Heap.
101+
4. Setup the initial range containing the root node.
102+
5. Run the Priority-First Search engine.
103+
6. Define an empty vertex visit predicate and vertex visit callback.
104+
7. Define the node enqueue predicate to only enqueue nodes that could yield paths shorter than those already discovered.
105+
8. Define the callback which constructs a stateful node for the algorithm queue.
106+
9. Update the global distance map to reflect the newly discovered shorter path.
107+
108+
> [!NOTE] Algorithm Desing
109+
>
110+
> The example above is very similar, though not the same, to how the Dijkstra's algorithm implementation is designed within the library.

0 commit comments

Comments
 (0)