diff --git a/docs/user-guide/algorithms/articulation_points.md b/docs/user-guide/algorithms/articulation_points.md index 8ee822e..fa3e44a 100644 --- a/docs/user-guide/algorithms/articulation_points.md +++ b/docs/user-guide/algorithms/articulation_points.md @@ -68,7 +68,8 @@ the order is unspecified. ## Signature ```cpp -void articulation_points(G&& g, OutputIterator cut_vertices); +void articulation_points(G&& g, OutputIterator cut_vertices, + const Alloc& alloc = Alloc()); ``` ## Parameters @@ -77,6 +78,7 @@ void articulation_points(G&& g, OutputIterator cut_vertices); |-----------|-------------| | `g` | Graph satisfying `adjacency_list` | | `cut_vertices` | Output iterator receiving vertex IDs of articulation points. Each vertex appears exactly once. | +| `alloc` | Allocator for internal stack storage. Default: `std::allocator{}`. | ## Supported Graph Properties diff --git a/docs/user-guide/algorithms/bfs.md b/docs/user-guide/algorithms/bfs.md index c3a29bc..3dde612 100644 --- a/docs/user-guide/algorithms/bfs.md +++ b/docs/user-guide/algorithms/bfs.md @@ -75,11 +75,13 @@ integer-indexed) and map-based (sparse vertex ID) graphs are supported. ```cpp // Multi-source BFS void breadth_first_search(G&& g, const Sources& sources, - Visitor&& visitor = empty_visitor()); + Visitor&& visitor = empty_visitor(), + const Alloc& alloc = Alloc()); // Single-source BFS void breadth_first_search(G&& g, const vertex_id_t& source, - Visitor&& visitor = empty_visitor()); + Visitor&& visitor = empty_visitor(), + const Alloc& alloc = Alloc()); ``` ## Parameters @@ -89,6 +91,7 @@ void breadth_first_search(G&& g, const vertex_id_t& source, | `g` | Graph satisfying `adjacency_list` | | `source` / `sources` | Source vertex ID or range of source vertex IDs | | `visitor` | Optional visitor struct with callback methods (see below). Default: `empty_visitor{}`. | +| `alloc` | Allocator for internal queue storage. Default: `std::allocator{}`. | ## Visitor Events diff --git a/docs/user-guide/algorithms/biconnected_components.md b/docs/user-guide/algorithms/biconnected_components.md index 5034002..18ebdf6 100644 --- a/docs/user-guide/algorithms/biconnected_components.md +++ b/docs/user-guide/algorithms/biconnected_components.md @@ -75,7 +75,8 @@ Key properties of the output: ## Signature ```cpp -void biconnected_components(G&& g, OuterContainer& components); +void biconnected_components(G&& g, OuterContainer& components, + const Alloc& alloc = Alloc()); ``` Where `OuterContainer` is typically `std::vector>>`. @@ -86,6 +87,7 @@ Where `OuterContainer` is typically `std::vector>>`. |-----------|-------------| | `g` | Graph satisfying `adjacency_list` | | `components` | Output container of containers. Each inner container holds vertex IDs in one biconnected component. Cleared and refilled by the algorithm. | +| `alloc` | Allocator for internal stack storage. Default: `std::allocator{}`. | ## Supported Graph Properties diff --git a/docs/user-guide/algorithms/connected_components.md b/docs/user-guide/algorithms/connected_components.md index 13bdf9c..73f166b 100644 --- a/docs/user-guide/algorithms/connected_components.md +++ b/docs/user-guide/algorithms/connected_components.md @@ -81,7 +81,8 @@ vertex v. ### `connected_components` — undirected ```cpp -size_t connected_components(G&& g, ComponentFn&& component); +size_t connected_components(G&& g, ComponentFn&& component, + const Alloc& alloc = Alloc()); ``` DFS-based algorithm for undirected graphs. Returns the number of connected @@ -91,10 +92,12 @@ component ID (0-based). ### `kosaraju` — strongly connected components ```cpp -void kosaraju(G&& g, GT&& g_transpose, ComponentFn&& component); +void kosaraju(G&& g, GT&& g_transpose, ComponentFn&& component, + const Alloc& alloc = Alloc()); // Bidirectional overload — no transpose graph needed -void kosaraju(G&& g, ComponentFn&& component); +void kosaraju(G&& g, ComponentFn&& component, + const Alloc& alloc = Alloc()); ``` Kosaraju's two-pass DFS algorithm for directed graphs. Fills @@ -126,6 +129,7 @@ rounds are performed before falling back to full edge iteration. Call | `g_transpose` | Transpose graph (for `kosaraju` and `afforest` with transpose). Must satisfy `adjacency_list`. | | `component` | For `connected_components` and `kosaraju`: callable `(const G&, vertex_id_t) -> ComponentID&` returning a mutable reference. For containers: wrap with `container_value_fn(comp)`. Must satisfy `vertex_property_fn_for`. For `afforest`: a subscriptable container (`vector` or `unordered_map`), still using the container API. | | `neighbor_rounds` | Number of neighbor-sampling rounds for `afforest` (default: 2) | +| `alloc` | Allocator for internal stack storage (`connected_components` and `kosaraju` only). Default: `std::allocator{}`. | **Return value (`connected_components` only):** `size_t` — number of connected components. `kosaraju` and `afforest` return `void`. diff --git a/docs/user-guide/algorithms/dfs.md b/docs/user-guide/algorithms/dfs.md index d6b4133..2e45c6a 100644 --- a/docs/user-guide/algorithms/dfs.md +++ b/docs/user-guide/algorithms/dfs.md @@ -83,7 +83,8 @@ sorting, strongly connected components, and many other graph analyses. ```cpp void depth_first_search(G&& g, const vertex_id_t& source, - Visitor&& visitor = empty_visitor()); + Visitor&& visitor = empty_visitor(), + const Alloc& alloc = Alloc()); ``` ## Parameters @@ -93,6 +94,7 @@ void depth_first_search(G&& g, const vertex_id_t& source, | `g` | Graph satisfying `adjacency_list` | | `source` | Source vertex ID to start DFS from | | `visitor` | Optional visitor struct with callback methods (see below). Default: `empty_visitor{}`. | +| `alloc` | Allocator for internal stack storage. Default: `std::allocator{}`. | ## Visitor Events diff --git a/docs/user-guide/algorithms/dijkstra.md b/docs/user-guide/algorithms/dijkstra.md index e4db6cc..f6ce85a 100644 --- a/docs/user-guide/algorithms/dijkstra.md +++ b/docs/user-guide/algorithms/dijkstra.md @@ -86,7 +86,8 @@ constexpr void dijkstra_shortest_paths(G&& g, const Sources& sources, WF&& weight = /* default returns 1 */, Visitor&& visitor = empty_visitor(), Compare&& compare = less<>{}, - Combine&& combine = plus<>{}); + Combine&& combine = plus<>{}, + const Alloc& alloc = Alloc()); // Single-source, distances + predecessors constexpr void dijkstra_shortest_paths(G&& g, const vertex_id_t& source, @@ -94,7 +95,8 @@ constexpr void dijkstra_shortest_paths(G&& g, const vertex_id_t& source, WF&& weight = /* default returns 1 */, Visitor&& visitor = empty_visitor(), Compare&& compare = less<>{}, - Combine&& combine = plus<>{}); + Combine&& combine = plus<>{}, + const Alloc& alloc = Alloc()); // Multi-source, distances only constexpr void dijkstra_shortest_distances(G&& g, const Sources& sources, @@ -102,7 +104,8 @@ constexpr void dijkstra_shortest_distances(G&& g, const Sources& sources, WF&& weight = /* default returns 1 */, Visitor&& visitor = empty_visitor(), Compare&& compare = less<>{}, - Combine&& combine = plus<>{}); + Combine&& combine = plus<>{}, + const Alloc& alloc = Alloc()); // Single-source, distances only constexpr void dijkstra_shortest_distances(G&& g, const vertex_id_t& source, @@ -110,7 +113,8 @@ constexpr void dijkstra_shortest_distances(G&& g, const vertex_id_t& source, WF&& weight = /* default returns 1 */, Visitor&& visitor = empty_visitor(), Compare&& compare = less<>{}, - Combine&& combine = plus<>{}); + Combine&& combine = plus<>{}, + const Alloc& alloc = Alloc()); ``` ## Parameters @@ -125,6 +129,7 @@ constexpr void dijkstra_shortest_distances(G&& g, const vertex_id_t& source, | `visitor` | Optional visitor struct with callback methods (see below). Default: `empty_visitor{}`. | | `compare` | Comparison function for distance values. Default: `std::less<>{}`. | | `combine` | Combine function for distance + weight. Default: `std::plus<>{}`. | +| `alloc` | Allocator for internal priority queue storage. Default: `std::allocator{}`. | ## Visitor Events diff --git a/docs/user-guide/algorithms/mst.md b/docs/user-guide/algorithms/mst.md index bc2d5ec..a4d52bc 100644 --- a/docs/user-guide/algorithms/mst.md +++ b/docs/user-guide/algorithms/mst.md @@ -91,14 +91,18 @@ in-place to save memory). ```cpp // Copy-sort: input edge list is not modified -auto kruskal(EdgeList& edges, OutputIterator tree); +auto kruskal(EdgeList& edges, OutputIterator tree, + const Alloc& alloc = Alloc()); -auto kruskal(EdgeList& edges, OutputIterator tree, Compare compare); +auto kruskal(EdgeList& edges, OutputIterator tree, Compare compare, + const Alloc& alloc = Alloc()); // In-place sort: sorts edges in-place for lower memory usage -auto inplace_kruskal(EdgeList& edges, OutputIterator tree); +auto inplace_kruskal(EdgeList& edges, OutputIterator tree, + const Alloc& alloc = Alloc()); -auto inplace_kruskal(EdgeList& edges, OutputIterator tree, Compare compare); +auto inplace_kruskal(EdgeList& edges, OutputIterator tree, Compare compare, + const Alloc& alloc = Alloc()); ``` **Returns** `std::pair` — total MST weight and number of connected @@ -111,6 +115,7 @@ components. | `edges` | Range of edge descriptors with `.source_id`, `.target_id`, `.value` | | `tree` | Output iterator receiving MST edges | | `compare` | Edge-value comparator. Default: `std::less<>{}`. Use `std::greater<>{}` for max spanning tree. | +| `alloc` | Allocator for internal union-find storage (`inplace_kruskal`) or edge copy + union-find (`kruskal`). Default: `std::allocator{}`. | ## Prim's Algorithm @@ -125,7 +130,8 @@ auto prim(G&& g, const vertex_id_t& seed, WeightFn&& weight, PredecessorFn&& predecessor, WF weight_fn = edge_value(g, uv), - Compare compare = std::less<>{}); + Compare compare = std::less<>{}, + const Alloc& alloc = Alloc()); ``` **Returns** the total MST weight. @@ -140,6 +146,7 @@ auto prim(G&& g, | `predecessor` | Callable `(const G&, vertex_id_t) -> P&` returning a mutable reference to the per-vertex predecessor. For containers: wrap with `container_value_fn(pred)`. Must satisfy `predecessor_fn_for`. | | `weight_fn` | Callable `WF(g, uv)` returning edge weight. Default: `edge_value(g, uv)`. | | `compare` | Comparator for weight values (default: `std::less<>{}`) | +| `alloc` | Allocator for internal priority queue storage. Default: `std::allocator{}`. | ## Edge Descriptor diff --git a/docs/user-guide/algorithms/tarjan_scc.md b/docs/user-guide/algorithms/tarjan_scc.md index 616d2e2..a681fd8 100644 --- a/docs/user-guide/algorithms/tarjan_scc.md +++ b/docs/user-guide/algorithms/tarjan_scc.md @@ -58,7 +58,8 @@ only **one DFS pass**, making it simpler to use when a transpose is unavailable. ## Algorithm ```cpp -size_t tarjan_scc(G&& g, ComponentFn&& component); +size_t tarjan_scc(G&& g, ComponentFn&& component, + const Alloc& alloc = Alloc()); ``` Single-pass iterative DFS using low-link values. Fills `component(g, uid)` with @@ -70,6 +71,7 @@ the SCC ID for each vertex and returns the total number of SCCs. |-----------|-------------| | `g` | Graph satisfying `adjacency_list` | | `component` | Callable `(const G&, vertex_id_t) -> ComponentID&` returning a mutable reference. For containers: wrap with `container_value_fn(comp)`. Must satisfy `vertex_property_fn_for`. | +| `alloc` | Allocator for internal stack storage. Default: `std::allocator{}`. | **Return value:** `size_t` — number of strongly connected components. diff --git a/docs/user-guide/algorithms/topological_sort.md b/docs/user-guide/algorithms/topological_sort.md index 96948f1..002dd47 100644 --- a/docs/user-guide/algorithms/topological_sort.md +++ b/docs/user-guide/algorithms/topological_sort.md @@ -83,13 +83,16 @@ Three overloads cover different use cases: ```cpp // Full-graph topological sort -bool topological_sort(const G& g, OutputIterator result); +bool topological_sort(const G& g, OutputIterator result, + const Alloc& alloc = Alloc()); // Single-source topological sort -bool topological_sort(const G& g, const vertex_id_t& source, OutputIterator result); +bool topological_sort(const G& g, const vertex_id_t& source, OutputIterator result, + const Alloc& alloc = Alloc()); // Multi-source topological sort -bool topological_sort(const G& g, const Sources& sources, OutputIterator result); +bool topological_sort(const G& g, const Sources& sources, OutputIterator result, + const Alloc& alloc = Alloc()); ``` > **Note:** Topological sort takes `const G&` (not a forwarding reference), unlike @@ -102,6 +105,7 @@ bool topological_sort(const G& g, const Sources& sources, OutputIterator result) | `g` | Graph satisfying `adjacency_list` (taken by `const&`) | | `source` / `sources` | Source vertex ID or range of source vertex IDs | | `result` | Output iterator receiving vertex IDs in topological order | +| `alloc` | Allocator for internal stack storage. Default: `std::allocator{}`. | **Return value:** `true` if the graph is a DAG (valid ordering produced), `false` if a cycle was detected. diff --git a/include/graph/algorithm/articulation_points.hpp b/include/graph/algorithm/articulation_points.hpp index 17f1471..87874e3 100644 --- a/include/graph/algorithm/articulation_points.hpp +++ b/include/graph/algorithm/articulation_points.hpp @@ -46,9 +46,11 @@ using adj_list::find_vertex; * * @tparam G The graph type. Must satisfy adjacency_list concept. * @tparam Iter The output iterator type. Must be output_iterator>. + * @tparam Alloc Allocator type for internal DFS stack storage. Defaults to std::allocator. * * @param g The graph. Callers must supply both directions of each undirected edge. * @param cut_vertices The output iterator where articulation point vertex IDs will be written. + * @param alloc Allocator instance used for the internal DFS stack (default: Alloc()) * * @return void. Articulation point IDs are written to the output iterator. * @@ -115,9 +117,9 @@ using adj_list::find_vertex; * // result contains {1, 2} (in some order) * ``` */ -template +template > requires std::output_iterator> -void articulation_points(G&& g, Iter cut_vertices) { +void articulation_points(G&& g, Iter cut_vertices, const Alloc& alloc = Alloc()) { using vid_t = vertex_id_t; const size_t N = num_vertices(g); @@ -154,7 +156,8 @@ void articulation_points(G&& g, Iter cut_vertices) { bool parent_edge_skipped; }; - std::stack stk; + using FrameAlloc = typename std::allocator_traits::template rebind_alloc; + std::stack> stk{std::deque(FrameAlloc(alloc))}; // Outer loop: handle disconnected graphs for (auto [start] : views::basic_vertexlist(g)) { diff --git a/include/graph/algorithm/biconnected_components.hpp b/include/graph/algorithm/biconnected_components.hpp index e4e841f..96b13e7 100644 --- a/include/graph/algorithm/biconnected_components.hpp +++ b/include/graph/algorithm/biconnected_components.hpp @@ -57,6 +57,8 @@ using adj_list::find_vertex; * @tparam G The graph type. Must satisfy adjacency_list concept. * @tparam OuterContainer A container of containers for the output components. * Typically std::vector>>. + * @tparam Alloc Allocator type for internal edge stack and DFS stack storage. + * Defaults to std::allocator. * * @param g The graph to process. Callers must supply both directions of each * undirected edge. @@ -64,6 +66,8 @@ using adj_list::find_vertex; * component found. Articulation-point vertices appear in multiple inner * containers. No ordering guarantee on the order of components or vertex * IDs within a component. + * @param alloc Allocator instance used for the internal edge stack and DFS stack + * (default: Alloc()) * * @return void. Results are stored in the components output parameter. * @@ -141,8 +145,8 @@ using adj_list::find_vertex; * } * ``` */ -template -void biconnected_components(G&& g, OuterContainer& components) { +template > +void biconnected_components(G&& g, OuterContainer& components, const Alloc& alloc = Alloc()) { using vid_t = vertex_id_t; using inner_type = typename OuterContainer::value_type; @@ -163,7 +167,8 @@ void biconnected_components(G&& g, OuterContainer& components) { // When a biconnected component boundary is detected, edges are popped to // extract the vertex set of that component. using edge_pair = std::pair; - std::stack edge_stk; + using EdgePairAlloc = typename std::allocator_traits::template rebind_alloc; + std::stack> edge_stk{std::deque(EdgePairAlloc(alloc))}; // Deduce the iterator type for edge ranges returned by edges(g, uid). // edge_descriptor_view iterators store the underlying edge_storage (an @@ -182,7 +187,8 @@ void biconnected_components(G&& g, OuterContainer& components) { bool parent_edge_skipped; }; - std::stack stk; + using FrameAlloc = typename std::allocator_traits::template rebind_alloc; + std::stack> stk{std::deque(FrameAlloc(alloc))}; // Helper: pop edges from edge_stk until (u, v) is popped (inclusive). // Collect unique vertex IDs and push_back as a new component. diff --git a/include/graph/algorithm/breadth_first_search.hpp b/include/graph/algorithm/breadth_first_search.hpp index cd57a28..aefb0b4 100644 --- a/include/graph/algorithm/breadth_first_search.hpp +++ b/include/graph/algorithm/breadth_first_search.hpp @@ -49,13 +49,15 @@ using adj_list::find_vertex; * 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 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. * - * @param g The graph to traverse (forwarding reference) + * @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. * @@ -153,15 +155,18 @@ using adj_list::find_vertex; * @see views::vertices_bfs BFS view for range-based traversal * @see connected_components For component detection using BFS */ -template +template > requires std::convertible_to, vertex_id_t> void breadth_first_search(G&& g, // graph const Sources& sources, - Visitor&& visitor = empty_visitor()) { + Visitor&& visitor = empty_visitor(), + const Alloc& alloc = Alloc()) { using id_type = vertex_id_t; // Initialize BFS data structures - std::queue Q; // FIFO queue for level-order traversal + using IdAlloc = typename std::allocator_traits::template rebind_alloc; + std::queue> Q{std::deque(IdAlloc(alloc))}; // FIFO queue for level-order traversal auto visited = make_vertex_property_map(g, false); // Track visited vertices to prevent cycles // Initialize all source vertices @@ -229,12 +234,14 @@ void breadth_first_search(G&& g, // graph * 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 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. * - * @param g The graph to traverse (forwarding reference) - * @param source Starting vertex ID + * @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. * @@ -262,13 +269,14 @@ void breadth_first_search(G&& g, // graph * @see breadth_first_search(G&&, Sources&&, Visitor&&) Multi-source version * @see views::vertices_bfs BFS view for range-based traversal */ -template +template > void breadth_first_search(G&& g, // graph const vertex_id_t& source, // starting vertex_id - Visitor&& visitor = empty_visitor()) { + Visitor&& visitor = empty_visitor(), + const Alloc& alloc = Alloc()) { // Wrap single source in array and delegate to multi-source version std::array, 1> sources{source}; - breadth_first_search(std::forward(g), sources, std::forward(visitor)); + breadth_first_search(std::forward(g), sources, std::forward(visitor), alloc); } } // namespace graph diff --git a/include/graph/algorithm/connected_components.hpp b/include/graph/algorithm/connected_components.hpp index c629c45..b3ef327 100644 --- a/include/graph/algorithm/connected_components.hpp +++ b/include/graph/algorithm/connected_components.hpp @@ -63,11 +63,15 @@ using adj_list::target_id; * @tparam ComponentFn Callable providing per-vertex component ID access: * (const G&, vertex_id_t) -> ComponentID&. Must satisfy * vertex_property_fn_for. + * @tparam Alloc Allocator type for internal finish-order vector and DFS stacks. + * Defaults to std::allocator. * * @param g The directed graph to analyze * @param g_t The transpose of graph g (edges reversed) * @param component Callable providing per-vertex component access: component(g, uid) -> ComponentID&. * For containers: wrap with container_value_fn(c). + * @param alloc Allocator instance used for the internal finish-order vector and DFS stacks + * (default: Alloc()) * * @return void. Results are stored in the component output parameter. * @@ -138,11 +142,13 @@ using adj_list::target_id; */ template + class ComponentFn, + class Alloc = std::allocator> requires vertex_property_fn_for void kosaraju(G&& g, // graph GT&& g_t, // graph transpose - ComponentFn&& component // out: strongly connected component assignment + ComponentFn&& component, // out: strongly connected component assignment + const Alloc& alloc = Alloc() ) { using CT = vertex_fn_value_t; auto visited = make_vertex_property_map, bool>(g, false); @@ -152,7 +158,8 @@ void kosaraju(G&& g, // graph } // Order stores vertex IDs (not descriptors) because the second pass // operates on g_t which has different descriptors than g. - std::vector> order; + using VidAlloc = typename std::allocator_traits::template rebind_alloc>; + std::vector, VidAlloc> order{VidAlloc(alloc)}; // Store a reference to avoid forwarding reference issues in lambda auto& g_ref = g; @@ -162,8 +169,10 @@ void kosaraju(G&& g, // graph // Stack stores vertex descriptors — 8-byte iterators, no string copies, // and O(1) vertex_id extraction via vertex_id(g, descriptor). using vertex_desc = vertex_t>; + using PairAlloc = typename std::allocator_traits::template rebind_alloc>; auto dfs_finish_order = [&](vertex_desc start) { - std::stack> stack; // (vertex, children_visited) + std::stack, std::deque, PairAlloc>> + stack{std::deque, PairAlloc>(PairAlloc(alloc))}; // (vertex, children_visited) stack.push({start, false}); visited[vertex_id(g_ref, start)] = true; @@ -199,12 +208,14 @@ void kosaraju(G&& g, // graph // Second pass: DFS on transpose graph in reverse finish order // Each DFS tree in this pass corresponds to exactly one SCC using gt_vertex_desc = vertex_t>; + using GtVDescAlloc = typename std::allocator_traits::template rebind_alloc; size_t cid = 0; std::ranges::reverse_view reverse{order}; for (auto& uid : reverse) { if (component(g, uid) == std::numeric_limits::max()) { // Manual iterative DFS on transpose graph using descriptors - std::stack dfs_stack; + std::stack> + dfs_stack{std::deque(GtVDescAlloc(alloc))}; dfs_stack.push(*find_vertex(g_t, uid)); component(g, uid) = cid; @@ -240,10 +251,14 @@ void kosaraju(G&& g, // graph * @tparam ComponentFn Callable providing per-vertex component ID access: * (const G&, vertex_id_t) -> ComponentID&. Must satisfy * vertex_property_fn_for. + * @tparam Alloc Allocator type for internal finish-order vector and DFS stacks. + * Defaults to std::allocator. * * @param g The directed bidirectional graph to analyze * @param component Callable providing per-vertex component access: component(g, uid) -> ComponentID&. * For containers: wrap with container_value_fn(c). + * @param alloc Allocator instance used for the internal finish-order vector and DFS stacks + * (default: Alloc()) * * @return void. Results are stored in the component output parameter. * @@ -284,10 +299,12 @@ void kosaraju(G&& g, // graph * @see kosaraju(G&&, GT&&, Component&) For non-bidirectional graphs */ template + class ComponentFn, + class Alloc = std::allocator> requires vertex_property_fn_for void kosaraju(G&& g, // bidirectional graph - ComponentFn&& component // out: strongly connected component assignment + ComponentFn&& component, // out: strongly connected component assignment + const Alloc& alloc = Alloc() ) { using CT = vertex_fn_value_t; auto visited = make_vertex_property_map, bool>(g, false); @@ -297,14 +314,17 @@ void kosaraju(G&& g, // bidirectional graph } // Both passes use the same graph, so descriptors are valid throughout. using vertex_desc = vertex_t>; - std::vector order; + using VDescAlloc = typename std::allocator_traits::template rebind_alloc; + std::vector order{VDescAlloc(alloc)}; auto& g_ref = g; // First pass: iterative DFS to compute finish times (same as two-graph version) // Stack stores vertex descriptors — lightweight, no string copies. + using PairAlloc = typename std::allocator_traits::template rebind_alloc>; auto dfs_finish_order = [&](vertex_desc start) { - std::stack> stack; + std::stack, std::deque, PairAlloc>> + stack{std::deque, PairAlloc>(PairAlloc(alloc))}; stack.push({start, false}); visited[vertex_id(g_ref, start)] = true; @@ -341,7 +361,8 @@ void kosaraju(G&& g, // bidirectional graph auto uid = vertex_id(g_ref, u); if (component(g, uid) == std::numeric_limits::max()) { // Manual iterative DFS using in_edges + source_id, storing descriptors - std::stack dfs_stack; + std::stack> + dfs_stack{std::deque(VDescAlloc(alloc))}; dfs_stack.push(u); component(g, uid) = cid; @@ -377,10 +398,13 @@ void kosaraju(G&& g, // bidirectional graph * @tparam ComponentFn Callable providing per-vertex component ID access: * (const G&, vertex_id_t) -> ComponentID&. Must satisfy * vertex_property_fn_for. + * @tparam Alloc Allocator type for the internal DFS stack storage. + * Defaults to std::allocator. * * @param g The graph to analyze (treated as undirected) * @param component Callable providing per-vertex component access: component(g, uid) -> ComponentID&. * For containers: wrap with container_value_fn(c). + * @param alloc Allocator instance used for the internal DFS stack (default: Alloc()) * * @return Number of connected components found * @@ -452,10 +476,12 @@ void kosaraju(G&& g, // bidirectional graph * @see afforest For faster parallel-friendly alternative */ template + class ComponentFn, + class Alloc = std::allocator> requires vertex_property_fn_for size_t connected_components(G&& g, // graph - ComponentFn&& component // out: connected component assignment + ComponentFn&& component, // out: connected component assignment + const Alloc& alloc = Alloc() ) { using CT = vertex_fn_value_t; // Initialize all components as unvisited @@ -466,7 +492,8 @@ size_t connected_components(G&& g, // graph // Stack of vertex descriptors — lightweight (8 bytes), avoids string copies, // and lets us call views::incidence(g, descriptor) without find_vertex on pop. using vertex_desc = vertex_t>; - std::stack S; + using VDescAlloc = typename std::allocator_traits::template rebind_alloc; + std::stack> S{std::deque(VDescAlloc(alloc))}; CT cid = 0; // Current component ID for (auto&& [uid, u] : views::vertexlist(g)) { if (component(g, uid) < std::numeric_limits::max()) { diff --git a/include/graph/algorithm/depth_first_search.hpp b/include/graph/algorithm/depth_first_search.hpp index 4bb37e0..96daffe 100644 --- a/include/graph/algorithm/depth_first_search.hpp +++ b/include/graph/algorithm/depth_first_search.hpp @@ -52,12 +52,14 @@ using adj_list::find_vertex; * Gray (discovered, in progress) -> Black (finished). This three-color scheme * enables precise classification of every edge encountered during traversal. * - * @tparam G Graph type satisfying adjacency_list concept + * @tparam G Graph type satisfying adjacency_list concept * @tparam Visitor Visitor type with optional callback methods + * @tparam Alloc Allocator type for internal stack storage. Defaults to std::allocator. * - * @param g The graph to traverse (forwarding reference) - * @param source Starting vertex ID + * @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 used for the internal DFS stack (default: Alloc()) * * @return void. Results delivered via visitor callbacks. * @@ -161,10 +163,11 @@ using adj_list::find_vertex; * @see breadth_first_search BFS algorithm for shortest-path traversal */ -template +template > void depth_first_search(G&& g, // graph const vertex_id_t& source, // starting vertex_id - Visitor&& visitor = empty_visitor()) { + Visitor&& visitor = empty_visitor(), + const Alloc& alloc = Alloc()) { using id_type = vertex_id_t; // Vertex color states for DFS @@ -219,7 +222,8 @@ void depth_first_search(G&& g, // graph visitor.on_discover_vertex(g, source); } - std::stack S; + using FrameAlloc = typename std::allocator_traits::template rebind_alloc; + std::stack> S{std::deque(FrameAlloc(alloc))}; { auto inc = views::incidence(g, *find_vertex(g, source)); S.push({source, std::ranges::begin(inc), std::ranges::end(inc)}); diff --git a/include/graph/algorithm/dijkstra_shortest_paths.hpp b/include/graph/algorithm/dijkstra_shortest_paths.hpp index 4bdf3e8..1f8a094 100644 --- a/include/graph/algorithm/dijkstra_shortest_paths.hpp +++ b/include/graph/algorithm/dijkstra_shortest_paths.hpp @@ -47,18 +47,20 @@ using adj_list::index_vertex_range; * Distance and predecessor are accessed through functions distance(g, uid) and predecessor(g, uid) * respectively, enabling the values to reside on vertex properties or in external containers. * - * @tparam G The graph type. Must satisfy adjacency_list concept. - * @tparam Sources Input range of source vertex IDs. - * @tparam DistanceFn Function returning a mutable reference to a per-vertex distance value: - * (const G&, vertex_id_t) -> Distance&. Must return an arithmetic type. + * @tparam G The graph type. Must satisfy adjacency_list concept. + * @tparam Sources Input range of source vertex IDs. + * @tparam DistanceFn Function returning a mutable reference to a per-vertex distance value: + * (const G&, vertex_id_t) -> Distance&. Must return an arithmetic type. * @tparam PredecessorFn Function returning a mutable reference to a per-vertex predecessor value: - * (const G&, vertex_id_t) -> PredecessorValue&. Can use _null_predecessor - * if path reconstruction is not needed. - * @tparam WF Edge weight function. Defaults to returning 1 for all edges (unweighted). - * @tparam Visitor Visitor type with callbacks for algorithm events. Defaults to empty_visitor. - * Visitor calls are optimized away if not used. - * @tparam Compare Comparison function for distance values. Defaults to less<>. - * @tparam Combine Function to combine distances and weights. Defaults to plus<>. + * (const G&, vertex_id_t) -> PredecessorValue&. Can use _null_predecessor + * if path reconstruction is not needed. + * @tparam WF Edge weight function. Defaults to returning 1 for all edges (unweighted). + * @tparam Visitor Visitor type with callbacks for algorithm events. Defaults to empty_visitor. + * Visitor calls are optimized away if not used. + * @tparam Compare Comparison function for distance values. Defaults to less<>. + * @tparam Combine Function to combine distances and weights. Defaults to plus<>. + * @tparam Alloc Allocator type for the internal priority queue storage. + * Defaults to std::allocator. * * @param g The graph to process. * @param sources Range of source vertex IDs to start from. @@ -68,6 +70,7 @@ using adj_list::index_vertex_range; * @param visitor Visitor for algorithm events (discover, examine, relax, finish). * @param compare Distance comparison function: (Distance, Distance) -> bool. * @param combine Distance combination function: (Distance, Weight) -> Distance. + * @param alloc Allocator instance used for the internal priority queue (default: Alloc()) * * @return void. Results are stored via the distance and predecessor functions. * @@ -174,7 +177,8 @@ template < class WF = function(const std::remove_reference_t&, const edge_t&)>, class Visitor = empty_visitor, class Compare = less>, - class Combine = plus>> + class Combine = plus>, + class Alloc = std::allocator> requires distance_fn_for && // predecessor_fn_for && // convertible_to, vertex_id_t> && // @@ -190,7 +194,8 @@ constexpr void dijkstra_shortest_paths( }, // default weight(g, uv) -> 1 Visitor&& visitor = empty_visitor(), Compare&& compare = less>(), - Combine&& combine = plus>()) { + Combine&& combine = plus>(), + const Alloc& alloc = Alloc()) { using graph_type = std::remove_reference_t; using id_type = vertex_id_t; using distance_type = distance_fn_value_t; @@ -233,8 +238,9 @@ constexpr void dijkstra_shortest_paths( 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); + using WVAlloc = typename std::allocator_traits::template rebind_alloc; + using Queue = std::priority_queue, decltype(qcompare)>; + Queue queue(qcompare, std::vector(WVAlloc(alloc))); // (The optimizer removes this loop if on_initialize_vertex() is empty.) if constexpr (has_on_initialize_vertex || has_on_initialize_vertex_id) { @@ -334,7 +340,9 @@ constexpr void dijkstra_shortest_paths( * * Convenience overload for single source vertex. See multi-source version for full documentation. * + * @tparam Alloc Allocator type for internal priority queue storage. Defaults to std::allocator. * @param source Single source vertex ID instead of range. + * @param alloc Allocator instance forwarded to the multi-source version (default: Alloc()) * * @see dijkstra_shortest_paths (multi-source overload) */ @@ -345,7 +353,8 @@ template < class WF = function(const std::remove_reference_t&, const edge_t&)>, class Visitor = empty_visitor, class Compare = less>, - class Combine = plus>> + class Combine = plus>, + class Alloc = std::allocator> requires distance_fn_for && // predecessor_fn_for && // basic_edge_weight_function, Compare, Combine> @@ -360,9 +369,10 @@ constexpr void dijkstra_shortest_paths( }, // default weight(g, uv) -> 1 Visitor&& visitor = empty_visitor(), Compare&& compare = less>(), - Combine&& combine = plus>()) { + Combine&& combine = plus>(), + const Alloc& alloc = Alloc()) { dijkstra_shortest_paths(g, subrange(&source, (&source + 1)), distance, predecessor, weight, - forward(visitor), forward(compare), forward(combine)); + forward(visitor), forward(compare), forward(combine), alloc); } /** @@ -378,6 +388,8 @@ constexpr void dijkstra_shortest_paths( * @tparam Visitor Visitor type with callbacks for algorithm events. Defaults to empty_visitor. * @tparam Compare Comparison function for distance values. Defaults to less<>. * @tparam Combine Function to combine distances and weights. Defaults to plus<>. + * @tparam Alloc Allocator type for the internal priority queue storage. + * Defaults to std::allocator. * * @param g The graph to process. * @param sources Range of source vertex IDs to start from. @@ -386,6 +398,7 @@ constexpr void dijkstra_shortest_paths( * @param visitor Visitor for algorithm events (discover, examine, relax, finish). * @param compare Distance comparison function: (Distance, Distance) -> bool. * @param combine Distance combination function: (Distance, Weight) -> Distance. + * @param alloc Allocator instance forwarded to dijkstra_shortest_paths (default: Alloc()) * * @return void. Results are stored via the distance function. * @@ -403,7 +416,8 @@ template < class WF = function(const std::remove_reference_t&, const edge_t&)>, class Visitor = empty_visitor, class Compare = less>, - class Combine = plus>> + class Combine = plus>, + class Alloc = std::allocator> requires distance_fn_for && // convertible_to, vertex_id_t> && // basic_edge_weight_function, Compare, Combine> @@ -417,9 +431,10 @@ constexpr void dijkstra_shortest_distances( }, // default weight(g, uv) -> 1 Visitor&& visitor = empty_visitor(), Compare&& compare = less>(), - Combine&& combine = plus>()) { + Combine&& combine = plus>(), + const Alloc& alloc = Alloc()) { dijkstra_shortest_paths(g, sources, distance, _null_predecessor, forward(weight), forward(visitor), - forward(compare), forward(combine)); + forward(compare), forward(combine), alloc); } /** @@ -427,7 +442,11 @@ constexpr void dijkstra_shortest_distances( * * Convenience overload for single source vertex without predecessor tracking. * + * @tparam Alloc Allocator type for the internal priority queue storage. + * Defaults to std::allocator. + * * @param source Single source vertex ID instead of range. + * @param alloc Allocator instance forwarded to dijkstra_shortest_paths (default: Alloc()) * * @see dijkstra_shortest_distances(G&&, const Sources&, Distances&, WF&&, Visitor&&, Compare&&, Combine&&) */ @@ -437,7 +456,8 @@ template < class WF = function(const std::remove_reference_t&, const edge_t&)>, class Visitor = empty_visitor, class Compare = less>, - class Combine = plus>> + class Combine = plus>, + class Alloc = std::allocator> requires distance_fn_for && // basic_edge_weight_function, Compare, Combine> constexpr void dijkstra_shortest_distances( @@ -450,9 +470,10 @@ constexpr void dijkstra_shortest_distances( }, // default weight(g, uv) -> 1 Visitor&& visitor = empty_visitor(), Compare&& compare = less>(), - Combine&& combine = plus>()) { + Combine&& combine = plus>(), + const Alloc& alloc = Alloc()) { dijkstra_shortest_paths(g, subrange(&source, (&source + 1)), distance, _null_predecessor, forward(weight), - forward(visitor), forward(compare), forward(combine)); + forward(visitor), forward(compare), forward(combine), alloc); } } // namespace graph diff --git a/include/graph/algorithm/mst.hpp b/include/graph/algorithm/mst.hpp index 26c3c20..b0ca2d5 100644 --- a/include/graph/algorithm/mst.hpp +++ b/include/graph/algorithm/mst.hpp @@ -292,8 +292,8 @@ struct disjoint_element { size_t count = 0; ///< Rank for union-by-rank (approximate tree depth) }; -template -using disjoint_vector = std::vector>; +template >> +using disjoint_vector = std::vector, Alloc>; /** * @brief Find the root of the set containing a vertex. @@ -309,8 +309,8 @@ using disjoint_vector = std::vector>; * * **Complexity:** O(α(V)) amortized, where α is the inverse Ackermann function (effectively constant) */ -template -VId disjoint_find(disjoint_vector& subsets, VId vtx) { +template +VId disjoint_find(disjoint_vector& subsets, VId vtx) { // Phase 1: Find the root by following parent pointers VId parent = subsets[vtx].id; while (parent != subsets[parent].id) { @@ -341,8 +341,8 @@ VId disjoint_find(disjoint_vector& subsets, VId vtx) { * * **Complexity:** O(α(V)) amortized with path compression */ -template -void disjoint_union(disjoint_vector& subsets, VId u, VId v) { +template +void disjoint_union(disjoint_vector& subsets, VId u, VId v) { // Find root representatives of both vertices VId u_root = disjoint_find(subsets, u); VId v_root = disjoint_find(subsets, v); @@ -377,8 +377,8 @@ void disjoint_union(disjoint_vector& subsets, VId u, VId v) { * * **Complexity:** O(α(V)) amortized */ -template -bool disjoint_union_find(disjoint_vector& subsets, VId u, VId v) { +template +bool disjoint_union_find(disjoint_vector& subsets, VId u, VId v) { // Find root representatives of both vertices VId u_root = disjoint_find(subsets, u); VId v_root = disjoint_find(subsets, v); @@ -459,11 +459,14 @@ struct has_edge().edge, void())> : true_type { };*/ * Processes edges in sorted order by weight, using union-find to detect cycles. * Uses default comparison (operator<) for minimum spanning tree. * - * @tparam IELR Input edge list range type. - * @tparam OELR Output edge list range type. + * @tparam IELR Input edge list range type. + * @tparam OELR Output edge list range type. + * @tparam Alloc Allocator type for internal edge copy and disjoint-set storage. + * Defaults to std::allocator. * - * @param e Input edge list with source_id, target_id, and value members. - * @param t Output edge list for MST edges. Must support push_back() and reserve(). + * @param e Input edge list with source_id, target_id, and value members. + * @param t Output edge list for MST edges. Must support push_back() and reserve(). + * @param alloc Allocator instance used for internal edge copy and disjoint-set (default: Alloc()) * * @return std::pair with total MST weight and number of connected components. * @@ -505,9 +508,11 @@ struct has_edge().edge, void())> : true_type { };*/ * auto [total_weight, num_components] = kruskal(edges, mst); * ``` */ -template -auto kruskal(IELR&& e, OELR&& t) { - return kruskal(e, t, [](auto&& i, auto&& j) { return i < j; }); +template > +requires requires(Alloc a, std::size_t n) { a.allocate(n); } +auto kruskal(IELR&& e, OELR&& t, const Alloc& alloc = Alloc()) { + return kruskal(e, t, [](auto&& i, auto&& j) { return i < j; }, alloc); } /** @@ -519,10 +524,13 @@ auto kruskal(IELR&& e, OELR&& t) { * @tparam IELR Input edge list range type. * @tparam OELR Output edge list range type. * @tparam CompareOp Comparison operator type. + * @tparam Alloc Allocator type for internal edge copy and disjoint-set storage. + * Defaults to std::allocator. * * @param e Input edge list with source_id, target_id, and value members. * @param t Output edge list for spanning tree edges. Must support push_back() and reserve(). * @param compare Comparison function: compare(ev1, ev2) returns true if ev1 should be processed first. + * @param alloc Allocator instance used for internal edge copy and disjoint-set (default: Alloc()) * * @return std::pair with total weight and number of connected components. * @@ -562,10 +570,12 @@ auto kruskal(IELR&& e, OELR&& t) { * auto [total_weight, components] = kruskal(edges, max_st, std::greater{}); * ``` */ -template -auto kruskal(IELR&& e, // graph - OELR&& t, // tree - CompareOp compare // edge value comparator +template > +auto kruskal(IELR&& e, // graph + OELR&& t, // tree + CompareOp compare, // edge value comparator + const Alloc& alloc = Alloc() ) { using edge_data = range_value_t; using VId = remove_const_t; @@ -578,7 +588,9 @@ auto kruskal(IELR&& e, // graph } // Copy edges to allow sorting without modifying input - std::vector> e_copy; + using TupleType = tuple; + using TupleAlloc = typename std::allocator_traits::template rebind_alloc; + std::vector e_copy{TupleAlloc(alloc)}; std::ranges::transform(e, back_inserter(e_copy), [](auto&& ed) { return std::make_tuple(ed.source_id, ed.target_id, ed.value); }); @@ -597,7 +609,8 @@ auto kruskal(IELR&& e, // graph std::ranges::sort(e_copy, outer_compare); // Initialize disjoint-set: each vertex starts in its own set - disjoint_vector subsets(N + 1); // Size N+1 to accommodate vertices 0 through N + using DElemAlloc = typename std::allocator_traits::template rebind_alloc>; + disjoint_vector subsets(N + 1, disjoint_element{}, DElemAlloc(alloc)); // Size N+1 to accommodate vertices 0 through N for (VId uid = 0; uid <= N; ++uid) { subsets[uid].id = uid; // Each vertex is its own parent (root) subsets[uid].count = 0; // Initial rank is 0 @@ -635,11 +648,14 @@ auto kruskal(IELR&& e, // graph * * Memory-efficient variant that sorts the input edge list directly instead of copying. * - * @tparam IELR Input edge list range type (must be permutable). - * @tparam OELR Output edge list range type. + * @tparam IELR Input edge list range type (must be permutable). + * @tparam OELR Output edge list range type. + * @tparam Alloc Allocator type for the internal disjoint-set storage. + * Defaults to std::allocator. * - * @param e Input edge list (will be sorted by edge weight). - * @param t Output edge list for MST edges. + * @param e Input edge list (will be sorted by edge weight). + * @param t Output edge list for MST edges. + * @param alloc Allocator instance used for the internal disjoint-set (default: Alloc()) * * @return std::pair with total MST weight and number of connected components. * @@ -674,10 +690,11 @@ auto kruskal(IELR&& e, // graph * **Remarks:** * - Use when input edge list is no longer needed in original order */ -template -requires std::permutable> -auto inplace_kruskal(IELR&& e, OELR&& t) { - return inplace_kruskal(e, t, [](auto&& i, auto&& j) { return i < j; }); +template > +requires std::permutable> && requires(Alloc a, std::size_t n) { a.allocate(n); } +auto inplace_kruskal(IELR&& e, OELR&& t, const Alloc& alloc = Alloc()) { + return inplace_kruskal(e, t, [](auto&& i, auto&& j) { return i < j; }, alloc); } /** @@ -689,10 +706,13 @@ auto inplace_kruskal(IELR&& e, OELR&& t) { * @tparam IELR Input edge list range type (must be permutable). * @tparam OELR Output edge list range type. * @tparam CompareOp Comparison operator type. + * @tparam Alloc Allocator type for the internal disjoint-set storage. + * Defaults to std::allocator. * * @param e Input edge list (will be sorted by comparison function). * @param t Output edge list for spanning tree edges. * @param compare Comparison function for edge values. + * @param alloc Allocator instance used for the internal disjoint-set (default: Alloc()) * * @return std::pair with total weight and number of connected components. * @@ -725,11 +745,13 @@ auto inplace_kruskal(IELR&& e, OELR&& t) { * - Time: O(E log E) — dominated by edge sorting * - Space: O(V) for disjoint-set structure (no edge copy) */ -template +template > requires std::permutable> -auto inplace_kruskal(IELR&& e, // graph - OELR&& t, // tree - CompareOp compare // edge value comparator +auto inplace_kruskal(IELR&& e, // graph + OELR&& t, // tree + CompareOp compare, // edge value comparator + const Alloc& alloc = Alloc() ) { using edge_data = range_value_t; using VId = remove_const_t; @@ -762,7 +784,8 @@ auto inplace_kruskal(IELR&& e, // graph } // Initialize disjoint-set: each vertex starts in its own set - disjoint_vector subsets(N + 1); // Size N+1 to accommodate vertices 0 through N + using DElemAlloc = typename std::allocator_traits::template rebind_alloc>; + disjoint_vector subsets(N + 1, disjoint_element{}, DElemAlloc(alloc)); // Size N+1 to accommodate vertices 0 through N for (VId uid = 0; uid <= N; ++uid) { subsets[uid].id = uid; // Each vertex is its own parent (root) subsets[uid].count = 0; // Initial rank is 0 @@ -807,6 +830,8 @@ auto inplace_kruskal(IELR&& e, // graph * @tparam WeightFn Function type returning lvalue ref to edge weight for a vertex. * @tparam WF Edge weight function type. Defaults to edge_value(g, uv). * @tparam CompareOp Comparison operator type. Defaults to less<>. + * @tparam Alloc Allocator type for the internal priority queue storage. + * Defaults to std::allocator. * * @param g The graph to process. * @param seed Starting vertex for MST growth. @@ -814,6 +839,7 @@ auto inplace_kruskal(IELR&& e, // graph * @param predecessor Function predecessor(g, uid) -> vertex_id&: returns lvalue ref to predecessor for vertex uid. * @param weight_fn Edge weight function (default: edge_value). * @param compare Comparison for edge weights (default: less<>). + * @param alloc Allocator instance forwarded to dijkstra_shortest_paths (default: Alloc()) * * @return Total weight of the spanning tree. * @@ -869,7 +895,8 @@ template (const std::remove_reference_t&, const edge_t&)>, - class CompareOp = less>> + class CompareOp = less>, + class Alloc = std::allocator> requires distance_fn_for && is_arithmetic_v> && predecessor_fn_for && @@ -882,7 +909,8 @@ auto prim(G&& g, // graph [](const auto& gr, const edge_t& uv) { return edge_value(gr, uv); }, // default weight_fn(g, uv) -> edge_value(g, uv) - CompareOp compare = less>() // edge value comparator + CompareOp compare = less>(), // edge value comparator + const Alloc& alloc = Alloc() ) { using edge_value_type = distance_fn_value_t; @@ -895,7 +923,7 @@ auto prim(G&& g, // graph std::forward(weight), std::forward(predecessor), std::forward(weight_fn), empty_visitor(), - std::forward(compare), prim_combine); + std::forward(compare), prim_combine, alloc); // Calculate total MST weight by summing edge weights edge_value_type total_weight = edge_value_type{}; diff --git a/include/graph/algorithm/tarjan_scc.hpp b/include/graph/algorithm/tarjan_scc.hpp index c54477e..82a4eb9 100644 --- a/include/graph/algorithm/tarjan_scc.hpp +++ b/include/graph/algorithm/tarjan_scc.hpp @@ -50,10 +50,13 @@ using adj_list::find_vertex; * @tparam ComponentFn Callable providing per-vertex component ID access: * (const G&, vertex_id_t) -> ComponentID&. Must satisfy * vertex_property_fn_for. + * @tparam Alloc Allocator type for the internal SCC stack and DFS stack storage. + * Defaults to std::allocator. * * @param g The directed graph to analyze * @param component Callable providing per-vertex component access: component(g, uid) -> ComponentID&. * For containers: wrap with container_value_fn(component). + * @param alloc Allocator instance used for the internal SCC stack and DFS stack (default: Alloc()) * * @return Number of strongly connected components found * @@ -127,10 +130,12 @@ using adj_list::find_vertex; * @see connected_components For undirected graphs */ template + class ComponentFn, + class Alloc = std::allocator> requires vertex_property_fn_for size_t tarjan_scc(G&& g, // graph - ComponentFn&& component // out: strongly connected component assignment + ComponentFn&& component, // out: strongly connected component assignment + const Alloc& alloc = Alloc() ) { using vid_t = vertex_id_t; using CT = vertex_fn_value_t; @@ -155,7 +160,8 @@ size_t tarjan_scc(G&& g, // graph size_t cid = 0; // Tarjan's stack: vertices in the current DFS path and pending SCC assignment - std::stack scc_stack; + using VidAlloc = typename std::allocator_traits::template rebind_alloc; + std::stack> scc_stack{std::deque(VidAlloc(alloc))}; // Iterative DFS: store edge iterators per frame to avoid re-scanning adjacency lists using edge_iter_t = std::ranges::iterator_t()))>; @@ -166,7 +172,8 @@ size_t tarjan_scc(G&& g, // graph edge_iter_t it_end; }; - std::stack dfs; + using FrameAlloc = typename std::allocator_traits::template rebind_alloc; + std::stack> dfs{std::deque(FrameAlloc(alloc))}; // Outer loop: handle disconnected graphs for (auto [start] : views::basic_vertexlist(g)) { diff --git a/include/graph/algorithm/topological_sort.hpp b/include/graph/algorithm/topological_sort.hpp index 7cad92d..9432783 100644 --- a/include/graph/algorithm/topological_sort.hpp +++ b/include/graph/algorithm/topological_sort.hpp @@ -162,20 +162,23 @@ namespace detail { * * Performs iterative DFS from a source vertex, collecting finish order and detecting cycles. * - * @tparam G Graph type - * @tparam ColorMap Vertex property map type for colors - * @param g The graph - * @param source Starting vertex ID - * @param color Color map for tracking vertex state + * @tparam G Graph type + * @tparam ColorMap Vertex property map type for colors + * @tparam Alloc Allocator type for the internal DFS stack. + * @param g The graph + * @param source Starting vertex ID + * @param color Color map for tracking vertex state * @param finish_order Vector to collect vertices in finish order - * @param has_cycle Flag set to true if cycle detected + * @param has_cycle Flag set to true if cycle detected + * @param alloc Allocator instance used for the internal DFS stack */ - template + template void topological_sort_dfs_visit(const G& g, const vertex_id_t& source, ColorMap& color, - std::vector>& finish_order, - bool& has_cycle) { + auto& finish_order, + bool& has_cycle, + const Alloc& alloc) { using Color = TopoColor; using id_type = vertex_id_t; @@ -193,7 +196,8 @@ namespace detail { // Discover source and push its stack frame color[source] = Color::Gray; - std::stack S; + using FrameAlloc = typename std::allocator_traits::template rebind_alloc; + std::stack> S{std::deque(FrameAlloc(alloc))}; { auto inc = basic_incidence(g, source); S.push({source, std::ranges::begin(inc), std::ranges::end(inc)}); @@ -242,10 +246,14 @@ namespace detail { * @tparam G Graph type satisfying adjacency_list concept. * @tparam Sources Input range of source vertex IDs. * @tparam OutputIterator Output iterator for writing vertex IDs in topological order. + * @tparam Alloc Allocator type for the internal finish-order vector and DFS stack. + * Defaults to std::allocator. * * @param g The directed graph to sort. * @param sources Range of starting vertex IDs for traversal. * @param result Output iterator where vertex IDs are written in topological order. + * @param alloc Allocator instance used for the internal DFS stack and finish-order vector + * (default: Alloc()) * * @return true if reachable subgraph is acyclic, false if cycle detected. * @@ -302,17 +310,20 @@ namespace detail { * @see topological_sort(const G&, OutputIterator) for full-graph variant * @see topological_sort(const G&, vertex_id_t, OutputIterator) for single-source variant */ -template +template > requires std::convertible_to, vertex_id_t> && std::output_iterator> -bool topological_sort(const G& g, const Sources& sources, OutputIterator result) { +bool topological_sort(const G& g, const Sources& sources, OutputIterator result, + const Alloc& alloc = Alloc()) { using id_type = vertex_id_t; using Color = detail::TopoColor; // Lazy init: index graphs get a sized vector (value-init → White=0), // mapped graphs get an empty reserved map (absent key → White via get). auto color = make_vertex_property_map, Color>(g); - std::vector finish_order; + using IdAlloc = typename std::allocator_traits::template rebind_alloc; + std::vector finish_order{IdAlloc(alloc)}; finish_order.reserve(num_vertices(g)); bool has_cycle = false; @@ -320,7 +331,7 @@ bool topological_sort(const G& g, const Sources& sources, OutputIterator result) // Run DFS from each source (skipping already-visited vertices) for (auto source : sources) { if (vertex_property_map_get(color, source, Color::White) == Color::White) { - detail::topological_sort_dfs_visit(g, source, color, finish_order, has_cycle); + detail::topological_sort_dfs_visit(g, source, color, finish_order, has_cycle, alloc); if (has_cycle) { return false; // Cycle detected } @@ -341,10 +352,12 @@ bool topological_sort(const G& g, const Sources& sources, OutputIterator result) * * @tparam G Graph type satisfying adjacency_list concept. * @tparam OutputIterator Output iterator for writing vertex IDs in topological order. + * @tparam Alloc Allocator type for internal storage. Defaults to std::allocator. * * @param g The directed graph to sort. * @param source Starting vertex ID for traversal. * @param result Output iterator where vertex IDs are written in topological order. + * @param alloc Allocator instance forwarded to the multi-source version (default: Alloc()) * * @return true if reachable subgraph is acyclic, false if cycle detected. * @@ -388,12 +401,13 @@ bool topological_sort(const G& g, const Sources& sources, OutputIterator result) * @see topological_sort(const G&, OutputIterator) for full-graph variant * @see topological_sort(const G&, const Sources&, OutputIterator) for multi-source variant */ -template +template > requires std::output_iterator> -bool topological_sort(const G& g, const vertex_id_t& source, OutputIterator result) { +bool topological_sort(const G& g, const vertex_id_t& source, OutputIterator result, + const Alloc& alloc = Alloc()) { // Delegate to multi-source version with single source std::array, 1> sources = {source}; - return topological_sort(g, sources, result); + return topological_sort(g, sources, result, alloc); } /** @@ -404,9 +418,12 @@ bool topological_sort(const G& g, const vertex_id_t& source, OutputIterator r * * @tparam G Graph type satisfying adjacency_list concept. * @tparam OutputIterator Output iterator for writing vertex IDs in topological order. + * @tparam Alloc Allocator type for internal storage. Defaults to std::allocator. * * @param g The directed graph to sort. * @param result Output iterator where vertex IDs are written in topological order. + * @param alloc Allocator instance used for the internal finish-order vector and DFS stack + * (default: Alloc()) * * @return true if graph is acyclic, false if cycle detected. * @@ -454,16 +471,17 @@ bool topological_sort(const G& g, const vertex_id_t& source, OutputIterator r * @see topological_sort(const G&, vertex_id_t, OutputIterator) for single-source variant * @see topological_sort(const G&, const Sources&, OutputIterator) for multi-source variant */ -template +template > requires std::output_iterator> -bool topological_sort(const G& g, OutputIterator result) { +bool topological_sort(const G& g, OutputIterator result, const Alloc& alloc = Alloc()) { using id_type = vertex_id_t; using Color = detail::TopoColor; // Lazy init: index graphs get a sized vector (value-init → White=0), // mapped graphs get an empty reserved map (absent key → White via get). auto color = make_vertex_property_map, Color>(g); - std::vector finish_order; + using IdAlloc = typename std::allocator_traits::template rebind_alloc; + std::vector finish_order{IdAlloc(alloc)}; finish_order.reserve(num_vertices(g)); bool has_cycle = false; @@ -472,7 +490,7 @@ bool topological_sort(const G& g, OutputIterator result) { for (auto v : vertices(g)) { id_type vid = vertex_id(g, v); if (vertex_property_map_get(color, vid, Color::White) == Color::White) { - detail::topological_sort_dfs_visit(g, vid, color, finish_order, has_cycle); + detail::topological_sort_dfs_visit(g, vid, color, finish_order, has_cycle, alloc); if (has_cycle) { return false; // Cycle detected }