dasynq-daryheap.h 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. #ifndef DASYNQ_DARYHEAP_H_INCLUDED
  2. #define DASYNQ_DARYHEAP_H_INCLUDED
  3. #include <type_traits>
  4. #include <functional>
  5. #include <utility>
  6. #include <limits>
  7. #include <cstddef>
  8. #include "dasynq-svec.h"
  9. namespace dasynq {
  10. /**
  11. * Priority queue implementation based on a heap with parameterised fan-out. All nodes are stored
  12. * in a vector, with the root at position 0, and each node has N child nodes, at positions
  13. * (p * N + 1) through (p * N + N) where p is the (parent) node position.
  14. *
  15. * With N=2, this is a binary heap. Higher values of N may give better performance due to better
  16. * cache locality, but also increase fan-out which will (if too high) also reduce performance.
  17. *
  18. * The destructor will not clean up (destruct) objects that have been added to the queue. If the
  19. * destructor of the element type (T) is non-trivial, all handles should be de-allocated before
  20. * destroying the queue.
  21. *
  22. * Implementation details:
  23. *
  24. * Adding a node returns a "handle", which maintains an index into the heap. When the position of
  25. * a node in the heap changes, its handle must be updated (the advantage is that changing priority
  26. * of or removing a node does not require a linear search for the node).
  27. *
  28. * Node data is actually stored as part of the handle, not in the queue.
  29. *
  30. * To add a node to the queue, it is inserted at the end and then "bubbled down" to its correct
  31. * location according to priority. To removing a node, the node is replaced with the last node in
  32. * the vector and then that is "bubbled up" to the correct position.
  33. *
  34. * Parameters:
  35. *
  36. * T : node data type
  37. * P : priority type (eg int)
  38. * Compare : functional object type to compare priorities
  39. * N : fan out factor (number of child nodes per node)
  40. */
  41. template <typename T, typename P, typename Compare = std::less<P>, int N = 4>
  42. class dary_heap
  43. {
  44. public:
  45. struct handle_t;
  46. using handle_t_r = handle_t &;
  47. private:
  48. static_assert(std::is_nothrow_move_assignable<P>::value, "P must be no-except move assignable");
  49. // Actual heap node
  50. class heap_node
  51. {
  52. public:
  53. P prio;
  54. handle_t * hnd;
  55. heap_node(handle_t * hnd_p, const P &prio_p) noexcept(std::is_nothrow_copy_constructible<P>::value)
  56. : prio(prio_p), hnd(hnd_p)
  57. {
  58. // nothing to do
  59. }
  60. heap_node() { }
  61. };
  62. svector<heap_node> hvec;
  63. using hindex_t = typename decltype(hvec)::size_type;
  64. hindex_t num_nodes = 0;
  65. public:
  66. // Handle to an element on the heap in the node buffer; also contains the data associated
  67. // with the node. (Alternative implementation would be to store the heap data in a
  68. // separate container, and have the handle be an index into that container).
  69. struct handle_t
  70. {
  71. union hd_u_t {
  72. // The data member is kept in a union so it doesn't get constructed/destructed
  73. // automatically, and we can construct it lazily.
  74. public:
  75. hd_u_t() { }
  76. ~hd_u_t() { }
  77. T hd;
  78. } hd_u;
  79. hindex_t heap_index;
  80. handle_t(const handle_t &) = delete;
  81. void operator=(const handle_t &) = delete;
  82. handle_t() { }
  83. };
  84. // Initialise a handle (if it does not have a suitable constructor). Need not do anything
  85. // but may store a sentinel value to mark the handle as inactive. It should not be
  86. // necessary to call this, really.
  87. static void init_handle(handle_t &h) noexcept
  88. {
  89. }
  90. private:
  91. // Bubble a newly added node down to the correct position
  92. bool bubble_down(hindex_t pos) noexcept
  93. {
  94. handle_t * ohndl = hvec[pos].hnd;
  95. P op = hvec[pos].prio;
  96. return bubble_down(pos, ohndl, op);
  97. }
  98. bool bubble_down(hindex_t pos, handle_t * ohndl, const P &op) noexcept
  99. {
  100. Compare lt;
  101. while (pos > 0) {
  102. hindex_t parent = (pos - 1) / N;
  103. if (! lt(op, hvec[parent].prio)) {
  104. break;
  105. }
  106. hvec[pos] = std::move(hvec[parent]);
  107. hvec[pos].hnd->heap_index = pos;
  108. pos = parent;
  109. }
  110. hvec[pos].hnd = ohndl;
  111. hvec[pos].prio = std::move(op);
  112. ohndl->heap_index = pos;
  113. return pos == 0;
  114. }
  115. void bubble_up(hindex_t pos = 0) noexcept
  116. {
  117. P p = hvec[pos].prio;
  118. handle_t &h = *(hvec[pos].hnd);
  119. bubble_up(pos, h, p);
  120. }
  121. void bubble_up(hindex_t pos, handle_t &h, const P &p) noexcept
  122. {
  123. hindex_t rmax = hvec.size() - 1;
  124. if (rmax == 0) {
  125. return;
  126. }
  127. Compare lt;
  128. hindex_t max = (rmax - 1) / N;
  129. while (pos <= max) {
  130. // Find (select) the smallest child node
  131. hindex_t lchild = pos * N + 1;
  132. hindex_t selchild = lchild;
  133. hindex_t rchild = std::min(lchild + N, rmax);
  134. for (hindex_t i = lchild + 1; i < rchild; i++) {
  135. if (lt(hvec[i].prio, hvec[selchild].prio)) {
  136. selchild = i;
  137. }
  138. }
  139. if (! lt(hvec[selchild].prio, p)) {
  140. break;
  141. }
  142. hvec[pos] = std::move(hvec[selchild]);
  143. hvec[pos].hnd->heap_index = pos;
  144. pos = selchild;
  145. }
  146. hvec[pos].hnd = &h;
  147. hvec[pos].prio = std::move(p);
  148. h.heap_index = pos;
  149. }
  150. void remove_h(hindex_t hidx) noexcept
  151. {
  152. hvec[hidx].hnd->heap_index = -1;
  153. if (hvec.size() != hidx + 1) {
  154. bubble_up(hidx, *(hvec.back().hnd), hvec.back().prio);
  155. hvec.pop_back();
  156. }
  157. else {
  158. hvec.pop_back();
  159. }
  160. }
  161. public:
  162. T & node_data(handle_t & index) noexcept
  163. {
  164. return index.hd_u.hd;
  165. }
  166. // Allocate a slot, but do not incorporate into the heap:
  167. // u... : parameters for data constructor T::T(...)
  168. template <typename ...U> void allocate(handle_t & hnd, U&&... u)
  169. {
  170. new (& hnd.hd_u.hd) T(std::forward<U>(u)...);
  171. hnd.heap_index = -1;
  172. // largest object size is PTRDIFF_MAX, so we expect the largest vector is that / sizeof node:
  173. constexpr hindex_t max_allowed = (std::numeric_limits<std::ptrdiff_t>::max() - 1) / sizeof(heap_node);
  174. if (num_nodes == max_allowed) {
  175. throw std::bad_alloc();
  176. }
  177. num_nodes++;
  178. if (__builtin_expect(hvec.capacity() < num_nodes, 0)) {
  179. hindex_t half_point = max_allowed / 2;
  180. try {
  181. if (__builtin_expect(num_nodes < half_point, 1)) {
  182. hvec.reserve(num_nodes * 2);
  183. }
  184. else {
  185. hvec.reserve(max_allowed);
  186. }
  187. }
  188. catch (std::bad_alloc &e) {
  189. hvec.reserve(num_nodes);
  190. }
  191. }
  192. }
  193. // Deallocate a slot
  194. void deallocate(handle_t & index) noexcept
  195. {
  196. num_nodes--;
  197. index.hd_u.hd.~T();
  198. // shrink the capacity of hvec if num_nodes is sufficiently less than
  199. // its current capacity:
  200. if (num_nodes < hvec.capacity() / 4) {
  201. hvec.shrink_to(num_nodes * 2);
  202. }
  203. }
  204. bool insert(handle_t & hnd) noexcept
  205. {
  206. P pval = P();
  207. return insert(hnd, pval);
  208. }
  209. bool insert(handle_t & hnd, const P &pval) noexcept
  210. {
  211. hnd.heap_index = hvec.size();
  212. // emplace an empty node; data/prio will be stored via bubble_down.
  213. hvec.emplace_back();
  214. return bubble_down(hvec.size() - 1, &hnd, pval);
  215. }
  216. // Get the root node handle. (Returns a handle_t or reference to handle_t).
  217. handle_t & get_root() noexcept
  218. {
  219. return * hvec[0].hnd;
  220. }
  221. P &get_root_priority() noexcept
  222. {
  223. return hvec[0].prio;
  224. }
  225. void pull_root() noexcept
  226. {
  227. remove_h(0);
  228. }
  229. void remove(handle_t & hnd) noexcept
  230. {
  231. remove_h(hnd.heap_index);
  232. }
  233. bool empty() noexcept
  234. {
  235. return hvec.empty();
  236. }
  237. bool is_queued(handle_t & hnd) noexcept
  238. {
  239. return hnd.heap_index != (hindex_t) -1;
  240. }
  241. // Set a node priority. Returns true iff the node becomes the root node (and wasn't before).
  242. bool set_priority(handle_t & hnd, const P& p) noexcept
  243. {
  244. int heap_index = hnd.heap_index;
  245. Compare lt;
  246. if (lt(hvec[heap_index].prio, p)) {
  247. // Increase key
  248. hvec[heap_index].prio = p;
  249. bubble_up(heap_index);
  250. return false;
  251. }
  252. else {
  253. // Decrease key
  254. hvec[heap_index].prio = p;
  255. return bubble_down(heap_index);
  256. }
  257. }
  258. size_t size() noexcept
  259. {
  260. return hvec.size();
  261. }
  262. dary_heap() { }
  263. dary_heap(const dary_heap &) = delete;
  264. };
  265. }
  266. #endif