-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathbreadth_first_search.hpp
More file actions
288 lines (272 loc) · 11.3 KB
/
Copy pathbreadth_first_search.hpp
File metadata and controls
288 lines (272 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
/**
* @file breadth_first_search.hpp
*
* @brief Breadth-first search traversal algorithms for graphs.
*
* Breadth-first search (BFS) is a fundamental graph traversal algorithm that explores
* vertices in order of their distance from the source vertex(es). It visits all vertices
* at distance k before visiting any vertex at distance k+1, making it ideal for finding
* unweighted shortest paths, level-order traversal, and testing graph connectivity.
*
* This implementation provides both single-source and multi-source variants with
* customizable visitor callbacks for tracking traversal events.
*
* @copyright Copyright (c) 2024
*
* SPDX-License-Identifier: BSL-1.0
*
* @authors Andrew Lumsdaine, Phil Ratzloff
*/
#include "graph/graph.hpp"
#include "graph/views/incidence.hpp"
#include "graph/algorithm/traversal_common.hpp"
#include "graph/adj_list/vertex_property_map.hpp"
#include <array>
#include <queue>
#include <ranges>
#include <limits>
#ifndef GRAPH_BREADTH_FIRST_SEARCH_HPP
# define GRAPH_BREADTH_FIRST_SEARCH_HPP
namespace graph {
// Using declarations for new namespace structure
using adj_list::adjacency_list;
using adj_list::vertex_id_t;
using adj_list::find_vertex;
/**
* @brief Multi-source breadth-first search with visitor pattern.
*
* Performs breadth-first traversal starting from multiple source vertices simultaneously,
* calling visitor methods at key points during traversal. This is the fundamental BFS
* implementation that supports custom event callbacks for tracking algorithm progress.
*
* BFS explores vertices in waves: all vertices at distance k from any source are visited
* before any vertex at distance k+1. When multiple sources are provided, vertices reachable
* from any source are discovered in the first wave, making this useful for multi-source
* shortest path problems and parallel/concurrent reachability analysis.
*
* @tparam G Graph type satisfying adjacency_list concept
* @tparam Sources Input range of source vertex IDs
* @tparam Visitor Visitor type with optional callback methods
* @tparam Alloc Allocator type for internal queue storage. Defaults to std::allocator<std::byte>.
*
* @param g The graph to traverse (forwarding reference)
* @param sources Range of starting vertex IDs
* @param visitor Visitor object to receive traversal events (default: empty_visitor)
* @param alloc Allocator instance used for the internal FIFO queue (default: Alloc())
*
* @return void. Results delivered via visitor callbacks.
*
* **Mandates:**
* - G must satisfy adjacency_list (index or mapped vertex containers)
* - Sources must be input_range with values convertible to vertex_id_t<G>
* - Visitor callbacks (if present) must accept appropriate parameters
*
* **Preconditions:**
* - g must not be modified during traversal
* - All vertex IDs in sources must be valid vertex IDs in g
* - Visitor methods must not modify graph structure
*
* **Effects:**
* - Does not modify the graph g
* - Invokes visitor callbacks in BFS traversal order
* - Vertices are visited in level-order (distance from sources)
*
* **Postconditions:**
* - All vertices reachable from any source are visited exactly once
* - Visitor callbacks invoked in BFS order
* - Graph g is unchanged
*
* **Throws:**
* - std::bad_alloc if visited array or queue cannot allocate memory
* - May propagate exceptions from visitor callbacks
* - May propagate exceptions from container operations
* - Exception guarantee: Basic. If an exception is thrown, graph g remains unchanged;
* visitor state depends on implementation; partial traversal may have occurred.
*
* **Complexity:**
* - Time: O(V + E) — each vertex visited once, each edge examined once
* - Space: O(V) for visited array and queue
*
* **Remarks:**
* - Uses std::queue for FIFO vertex processing
* - Visited tracking: vector<bool> for index graphs, unordered_map for mapped graphs
* - No distance tracking (use BFS views for distances)
* - Multi-source as primary interface: single-source is a special case with no overhead
*
* **Visitor Callbacks:**
* - on_initialize_vertex(vertex_id): Called when vertex is added to initial sources
* - on_discover_vertex(vertex_id): Called when vertex is first encountered
* - on_examine_vertex(vertex_id): Called when vertex is dequeued for processing
* - on_examine_edge(edge): Called for each outgoing edge examined
* - on_finish_vertex(vertex_id): Called after all edges examined
* All callbacks are optional via SFINAE (has_on_* concept checks).
*
* **Supported Graph Properties:**
*
* Directedness:
* - ✅ Directed graphs
* - ✅ Undirected graphs
*
* Edge Properties:
* - ✅ Unweighted edges (BFS finds shortest paths)
* - ✅ Weighted edges (weights ignored, treats as unweighted)
* - ✅ Multi-edges (all edges examined, vertices visited once)
* - ✅ Self-loops (examined but don't affect traversal)
* - ✅ Cycles (visited tracking prevents infinite loops)
*
* Graph Structure:
* - ✅ Connected graphs
* - ✅ Disconnected graphs (visits reachable component)
* - ✅ Empty graphs (returns immediately)
*
* ## Example Usage
*
* ```cpp
* #include <graph/graph.hpp>
* #include <graph/algorithm/breadth_first_search.hpp>
*
* using namespace graph;
*
* // Basic traversal:
* Graph g({{0,1}, {1,2}, {2,3}});
* std::vector<uint32_t> sources = {0};
* breadth_first_search(g, sources); // Traverses 0->1->2->3
*
* // With custom visitor:
* struct PrintVisitor {
* void on_discover_vertex(auto& g, auto v) {
* std::cout << "Discovered: " << v << "\n";
* }
* };
* PrintVisitor visitor;
* breadth_first_search(g, sources, visitor);
*
* // Multi-source BFS:
* std::vector<uint32_t> multi_sources = {0, 5, 10};
* breadth_first_search(g, multi_sources); // Explores from all simultaneously
* ```
*
* @see breadth_first_search(G&&, vertex_id_t<G>, Visitor&&) Single-source convenience wrapper
* @see views::vertices_bfs BFS view for range-based traversal
* @see connected_components For component detection using BFS
*/
template <adjacency_list G, std::ranges::input_range Sources, class Visitor = empty_visitor,
class Alloc = std::allocator<std::byte>>
requires std::convertible_to<std::ranges::range_value_t<Sources>, vertex_id_t<G>>
void breadth_first_search(G&& g, // graph
const Sources& sources,
Visitor&& visitor = empty_visitor(),
const Alloc& alloc = Alloc()) {
static_assert(valid_visitor<G, Visitor>,
"Visitor has no recognized on_* callbacks. Check for a misspelled event name "
"(e.g. on_discover_vertx), or pass graph::empty_visitor{} for no callbacks.");
using id_type = vertex_id_t<G>;
// Initialize BFS data structures
using IdAlloc = typename std::allocator_traits<Alloc>::template rebind_alloc<id_type>;
std::queue<id_type, std::deque<id_type, IdAlloc>> Q{std::deque<id_type, IdAlloc>(IdAlloc(alloc))}; // FIFO queue for level-order traversal
auto visited = make_vertex_property_map<G, bool>(g, false); // Track visited vertices to prevent cycles
// Initialize all source vertices
for (auto uid : sources) {
// Notify visitor of initialization
if constexpr (has_on_initialize_vertex<G, Visitor>) {
visitor.on_initialize_vertex(g, *find_vertex(g, uid));
} else if constexpr (has_on_initialize_vertex_id<G, Visitor>) {
visitor.on_initialize_vertex(g, uid);
}
if constexpr (has_on_discover_vertex<G, Visitor>) {
visitor.on_discover_vertex(g, *find_vertex(g, uid));
} else if constexpr (has_on_discover_vertex_id<G, Visitor>) {
visitor.on_discover_vertex(g, uid);
}
// Mark source as visited and add to queue
visited[uid] = true;
Q.push(uid);
}
// Main BFS loop: process vertices in level-order
while (!Q.empty()) {
// Dequeue next vertex to examine
id_type uid = Q.front();
Q.pop();
// Notify visitor that we're examining this vertex
if constexpr (has_on_examine_vertex<G, Visitor>) {
visitor.on_examine_vertex(g, *find_vertex(g, uid));
} else if constexpr (has_on_examine_vertex_id<G, Visitor>) {
visitor.on_examine_vertex(g, uid);
}
// Explore all edges from current vertex
for (auto&& [vid, uv] : views::incidence(g, *find_vertex(g, uid))) {
// Notify visitor about this edge
if constexpr (has_on_examine_edge<G, Visitor>) {
visitor.on_examine_edge(g, uv);
}
// If target vertex not yet visited, discover it
if (!visited[vid]) {
visited[vid] = true; // Mark as visited before queueing
if constexpr (has_on_discover_vertex<G, Visitor>) {
visitor.on_discover_vertex(g, *find_vertex(g, vid));
} else if constexpr (has_on_discover_vertex_id<G, Visitor>) {
visitor.on_discover_vertex(g, vid);
}
Q.push(vid); // Add to queue for later examination
}
}
// Notify visitor that we've finished examining all edges from this vertex
if constexpr (has_on_finish_vertex<G, Visitor>) {
visitor.on_finish_vertex(g, *find_vertex(g, uid));
} else if constexpr (has_on_finish_vertex_id<G, Visitor>) {
visitor.on_finish_vertex(g, uid);
}
}
}
/**
* @brief Single-source breadth-first search with visitor pattern.
*
* Convenience wrapper for BFS starting from a single source vertex.
* Delegates to the multi-source version by wrapping the source in a std::array.
*
* @tparam G Graph type satisfying adjacency_list concept
* @tparam Visitor Visitor type with optional callback methods
* @tparam Alloc Allocator type for internal queue storage. Defaults to std::allocator<std::byte>.
*
* @param g The graph to traverse (forwarding reference)
* @param source Starting vertex ID
* @param visitor Visitor object to receive traversal events (default: empty_visitor)
* @param alloc Allocator instance forwarded to the multi-source version (default: Alloc())
*
* @return void. Results delivered via visitor callbacks.
*
* **Preconditions:**
* - source must be a valid vertex ID in g
*
* **Postconditions:**
* - All vertices reachable from source are visited exactly once
*
* **Throws:**
* - Exception guarantee: Basic (same as multi-source version).
*
* **Complexity:**
* - Time: O(V + E)
* - Space: O(V)
* - Identical to multi-source version; delegation overhead is negligible.
*
* ## Example Usage
*
* ```cpp
* Graph g({{0,1}, {1,2}, {2,3}});
* breadth_first_search(g, 0); // Start from vertex 0
* ```
*
* @see breadth_first_search(G&&, Sources&&, Visitor&&) Multi-source version
* @see views::vertices_bfs BFS view for range-based traversal
*/
template <adjacency_list G, class Visitor = empty_visitor, class Alloc = std::allocator<std::byte>>
void breadth_first_search(G&& g, // graph
const vertex_id_t<G>& start_vertex_id, // starting vertex_id
Visitor&& visitor = empty_visitor(),
const Alloc& alloc = Alloc()) {
// Wrap single source in array and delegate to multi-source version
std::array<vertex_id_t<G>, 1> sources{start_vertex_id};
breadth_first_search(std::forward<G>(g), sources, std::forward<Visitor>(visitor), alloc);
}
} // namespace graph
#endif // GRAPH_BREADTH_FIRST_SEARCH_HPP