diff --git a/D3128_Algorithms/src/dijkstra_heap_tags.hpp b/D3128_Algorithms/src/dijkstra_heap_tags.hpp new file mode 100644 index 0000000..1635e1b --- /dev/null +++ b/D3128_Algorithms/src/dijkstra_heap_tags.hpp @@ -0,0 +1,11 @@ +// Heap-selector tag: use std::priority_queue with lazy deletion (default). +// Heap entries may grow to O(E); stale entries are skipped at pop time. +struct use_default_heap {}; + +// Heap-selector tag: use an indexed d-ary heap with true decrease-key. +// Heap size is bounded by O(V); no stale pops. +// @tparam Arity Children per node (default 4, matching Boost's d_ary_heap_indirect). +template +struct use_indexed_dary_heap { + static constexpr size_t arity = Arity; +}; diff --git a/D3128_Algorithms/src/dijkstra_shortest_dists.hpp b/D3128_Algorithms/src/dijkstra_shortest_dists.hpp index 9bbc162..b02ce16 100644 --- a/D3128_Algorithms/src/dijkstra_shortest_dists.hpp +++ b/D3128_Algorithms/src/dijkstra_shortest_dists.hpp @@ -5,6 +5,7 @@ template >, class Combine = plus>, + class Heap = use_default_heap, class Alloc = allocator> requires distance_fn_for && basic_edge_weight_function, Compare, Combine> @@ -17,4 +18,5 @@ constexpr void dijkstra_shortest_distances( Visitor&& visitor = empty_visitor(), Compare&& compare = less>(), Combine&& combine = plus>(), + Heap heap_tag = Heap{}, const Alloc& alloc = Alloc()); diff --git a/D3128_Algorithms/src/dijkstra_shortest_dists_multi.hpp b/D3128_Algorithms/src/dijkstra_shortest_dists_multi.hpp index 8868b16..d977caf 100644 --- a/D3128_Algorithms/src/dijkstra_shortest_dists_multi.hpp +++ b/D3128_Algorithms/src/dijkstra_shortest_dists_multi.hpp @@ -6,6 +6,7 @@ template >, class Combine = plus>, + class Heap = use_default_heap, class Alloc = allocator> requires distance_fn_for && convertible_to, vertex_id_t> && @@ -19,4 +20,5 @@ constexpr void dijkstra_shortest_distances( Visitor&& visitor = empty_visitor(), Compare&& compare = less>(), Combine&& combine = plus>(), + Heap heap_tag = Heap{}, const Alloc& alloc = Alloc()); diff --git a/D3128_Algorithms/src/dijkstra_shortest_paths.hpp b/D3128_Algorithms/src/dijkstra_shortest_paths.hpp index 46c551f..4a66f31 100644 --- a/D3128_Algorithms/src/dijkstra_shortest_paths.hpp +++ b/D3128_Algorithms/src/dijkstra_shortest_paths.hpp @@ -6,6 +6,7 @@ template >, class Combine = plus>, + class Heap = use_default_heap, class Alloc = allocator> requires distance_fn_for && predecessor_fn_for && @@ -20,4 +21,5 @@ constexpr void dijkstra_shortest_paths( Visitor&& visitor = empty_visitor(), Compare&& compare = less>(), Combine&& combine = plus>(), + Heap heap_tag = Heap{}, const Alloc& alloc = Alloc()); diff --git a/D3128_Algorithms/src/dijkstra_shortest_paths_multi.hpp b/D3128_Algorithms/src/dijkstra_shortest_paths_multi.hpp index e309b4c..7841d1a 100644 --- a/D3128_Algorithms/src/dijkstra_shortest_paths_multi.hpp +++ b/D3128_Algorithms/src/dijkstra_shortest_paths_multi.hpp @@ -7,6 +7,7 @@ template >, class Combine = plus>, + class Heap = use_default_heap, class Alloc = allocator> requires distance_fn_for && predecessor_fn_for && @@ -22,4 +23,5 @@ constexpr void dijkstra_shortest_paths( Visitor&& visitor = empty_visitor(), Compare&& compare = less>(), Combine&& combine = plus>(), + Heap heap_tag = Heap{}, const Alloc& alloc = Alloc()); diff --git a/D3128_Algorithms/src/mst.hpp b/D3128_Algorithms/src/mst.hpp index 7c82e21..f329884 100644 --- a/D3128_Algorithms/src/mst.hpp +++ b/D3128_Algorithms/src/mst.hpp @@ -31,6 +31,7 @@ template (const remove_reference_t&, const edge_t&)>, class CompareOp = less>, + class Heap = use_default_heap, class Alloc = allocator> requires distance_fn_for && is_arithmetic_v> && @@ -43,4 +44,5 @@ auto prim(G&& g, PredecessorFn&& predecessor, WF&& weight_fn = [](const auto& gr, const edge_t& uv) { return edge_value(gr, uv); }, CompareOp compare = less>(), + Heap heap_tag = Heap{}, const Alloc& alloc = Alloc()); diff --git a/D3128_Algorithms/tex/algorithms.tex b/D3128_Algorithms/tex/algorithms.tex index b95a861..b5da5fd 100644 --- a/D3128_Algorithms/tex/algorithms.tex +++ b/D3128_Algorithms/tex/algorithms.tex @@ -748,8 +748,24 @@ \subsection{Dijkstra Shortest Paths and Shortest Distances} %\caption{Algorithm Example} \end{table} -Note that complexity may be $\mathcal{O}(|E| + |V|\log{|V|)}$ for certain implementations that use a Fibonacci heap -instead of a binary heap implemented with \tcode{std::priority_queue}. +Two heap-selector tag types control the internal priority queue strategy and are passed as the +\tcode{Heap} template argument: +{\small + \lstinputlisting[texcl=false]{D3128_Algorithms/src/dijkstra_heap_tags.hpp} +} + +The \tcode{Heap} template parameter selects the internal priority queue strategy at compile time. +Two heap-selector tags are provided: +\begin{itemize} + \item \tcode{use_default_heap} (default) --- uses \tcode{std::priority_queue} with lazy deletion. + Stale entries are skipped at pop time; the heap may grow to $\mathcal{O}(|E|)$ in the worst case. + Recommended for sparse graphs ($|E|/|V| \lesssim 4$), grid-like topologies, and path graphs. + \item \tcode{use_indexed_dary_heap} --- uses an indexed $d$-ary heap with true decrease-key. + Heap size is bounded by $\mathcal{O}(|V|)$; no stale pops. + Recommended for dense or hub-heavy graphs ($|E|/|V| \gtrsim 8$). + \tcode{Arity=8} is the recommended setting on x86\_64 for high-$|E|/|V|$ workloads; + \tcode{Arity=4} matches Boost's \tcode{d_ary_heap_indirect}. +\end{itemize} \subsubsection{Dijkstra Shortest Paths} @@ -844,9 +860,12 @@ \subsubsection{Dijkstra Shortest Paths} \end{itemize} \pnum\complexity \begin{itemize} - \item \textbf{Time:} $\mathcal{O}((|E| + |V|)\log{|V|})$ based on using the binary heap in \tcode{std::priority_queue}. - \item \textbf{Space:} $\mathcal{O}(|V|)$ auxiliary --- priority queue and bookkeeping arrays. - \item An implementation may choose to use a Fibonacci heap for a time complexity of $\mathcal{O}(|E| + |V|\log{|V|})$. + \item \textbf{Time:} $\mathcal{O}((|E| + |V|)\log{|V|})$ with \tcode{use_default_heap} + (binary heap via \tcode{std::priority_queue}). + With \tcode{use_indexed_dary_heap} the heap size is bounded by $\mathcal{O}(|V|)$ + and each operation costs $\mathcal{O}(\log_{d}{|V|})$. + \item \textbf{Space:} $\mathcal{O}(|V|)$ auxiliary for \tcode{use_indexed_dary_heap}; + up to $\mathcal{O}(|E|)$ for \tcode{use_default_heap} due to lazy deletion. \end{itemize} \pnum\remarks \begin{itemize} @@ -858,6 +877,11 @@ \subsubsection{Dijkstra Shortest Paths} the same way (see §\textit{Vertex Property Function Concepts}). \item Pass \lstinline{_null_predecessor} as \lstinline{predecessor} when predecessor tracking is not needed (avoids storing results). + \item The \lstinline{Heap} template parameter selects the internal heap implementation. + Pass \tcode{use_default_heap\{\}} (default) for sparse or grid-like graphs; + pass \tcode{use_indexed_dary_heap\{\}} for dense or hub-heavy graphs + where many edges trigger distance relaxation. + \tcode{use_indexed_dary_heap<8>} is recommended on x86\_64 for high-$|E|/|V|$ workloads. \item The optional \lstinline{alloc} parameter supplies an allocator used for the internal priority queue. Defaults to \tcode{std::allocator}. Provide a custom allocator (e.g.~a pool allocator) to control the memory @@ -953,9 +977,12 @@ \subsubsection{Dijkstra Shortest Distances} %\pnum\returns \lstinline{void} \\ \pnum\complexity \begin{itemize} - \item \textbf{Time:} $\mathcal{O}((|E| + |V|)\log{|V|})$ based on using the binary heap in \tcode{std::priority_queue}. - \item \textbf{Space:} $\mathcal{O}(|V|)$ auxiliary --- priority queue and bookkeeping arrays. - \item An implementation may choose to use a Fibonacci heap for a time complexity of $\mathcal{O}(|E| + |V|\log{|V|})$. + \item \textbf{Time:} $\mathcal{O}((|E| + |V|)\log{|V|})$ with \tcode{use_default_heap} + (binary heap via \tcode{std::priority_queue}). + With \tcode{use_indexed_dary_heap} the heap size is bounded by $\mathcal{O}(|V|)$ + and each operation costs $\mathcal{O}(\log_{d}{|V|})$. + \item \textbf{Space:} $\mathcal{O}(|V|)$ auxiliary for \tcode{use_indexed_dary_heap}; + up to $\mathcal{O}(|E|)$ for \tcode{use_default_heap} due to lazy deletion. \end{itemize} \pnum\remarks \begin{itemize} @@ -964,6 +991,11 @@ \subsubsection{Dijkstra Shortest Distances} \item \tcode{DistanceFn} must satisfy \tcode{distance_fn_for}. Wrap a \tcode{std::vector} or \tcode{std::unordered_map} with \tcode{container_value_fn} (see §\textit{Vertex Property Function Concepts}). + \item The \lstinline{Heap} template parameter selects the internal heap implementation. + Pass \tcode{use_default_heap\{\}} (default) for sparse or grid-like graphs; + pass \tcode{use_indexed_dary_heap\{\}} for dense or hub-heavy graphs + where many edges trigger distance relaxation. + \tcode{use_indexed_dary_heap<8>} is recommended on x86\_64 for high-$|E|/|V|$ workloads. \item The optional \lstinline{alloc} parameter supplies an allocator used for the internal priority queue. Defaults to \tcode{std::allocator}. Provide a custom allocator (e.g.~a pool allocator) to control the memory @@ -2168,7 +2200,7 @@ \subsection{Prim Minimum Spanning Tree} \end{table} {\small - \lstinputlisting[firstline=28,lastline=46]{D3128_Algorithms/src/mst.hpp} + \lstinputlisting[firstline=28,lastline=48]{D3128_Algorithms/src/mst.hpp} } \begin{itemdescr} @@ -2220,14 +2252,25 @@ \subsection{Prim Minimum Spanning Tree} \end{itemize} \pnum\complexity \begin{itemize} - \item \textbf{Time:} $\mathcal{O}(|E|\log{|V|})$. - \item \textbf{Space:} $\mathcal{O}(|V|)$ auxiliary --- priority queue and bookkeeping arrays. + \item \textbf{Time:} $\mathcal{O}(|E|\log{|V|})$ with \tcode{use_default_heap}. + With \tcode{use_indexed_dary_heap} the heap size is bounded by $\mathcal{O}(|V|)$ + and each operation costs $\mathcal{O}(\log_{d}{|V|})$. + \item \textbf{Space:} $\mathcal{O}(|V|)$ auxiliary for \tcode{use_indexed_dary_heap}; + up to $\mathcal{O}(|E|)$ for \tcode{use_default_heap} due to lazy deletion. + \end{itemize} + \pnum\remarks + \begin{itemize} + \item Only produces a spanning tree for the connected component containing \lstinline{seed}. + For disconnected graphs, call \lstinline{prim} once per component with a seed vertex from each component. + \item The \lstinline{Heap} template parameter selects the internal heap implementation + (forwarded to \lstinline{dijkstra_shortest_paths}). + Pass \tcode{use_default_heap\{\}} (default) for sparse or grid-like graphs; + pass \tcode{use_indexed_dary_heap\{\}} for dense or hub-heavy graphs. + See the Dijkstra Shortest Paths section for guidance on arity selection. + \item The optional \lstinline{alloc} parameter supplies an allocator used for + the internal priority queue (forwarded to \lstinline{dijkstra_shortest_paths}). + Defaults to \tcode{std::allocator}. \end{itemize} - \pnum\remarks Only produces a spanning tree for the connected component containing \lstinline{seed}. - For disconnected graphs, call \lstinline{prim} once per component with a seed vertex from each component. - The optional \lstinline{alloc} parameter supplies an allocator used for - the internal priority queue (forwarded to \lstinline{dijkstra_shortest_paths}). - Defaults to \tcode{std::allocator}. %\pnum\errors \end{itemdescr}