1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 2 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 3 4<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> 5<head> 6 <meta name="generator" content= 7 "HTML Tidy for Linux/x86 (vers 12 April 2005), see www.w3.org" /> 8 9 <title>Priority-Queues</title> 10 <meta http-equiv="Content-Type" content= 11 "text/html; charset=us-ascii" /> 12 </head> 13 14<body> 15 <div id="page"> 16 <h1>Priority-Queue Design</h1> 17 18 <h2><a name="overview" id="overview">Overview</a></h2> 19 20 <p>The priority-queue container has the following 21 declaration:</p> 22 <pre> 23<b>template</b>< 24 <b>typename</b> Value_Type, 25 <b>typename</b> Cmp_Fn = std::less<Value_Type>, 26 <b>typename</b> Tag = <a href="pairing_heap_tag.html">pairing_heap_tag</a>, 27 <b>typename</b> Allocator = std::allocator<<b>char</b>> > 28<b>class</b> <a href="priority_queue.html">priority_queue</a>; 29</pre> 30 31 <p>The parameters have the following meaning:</p> 32 33 <ol> 34 <li><tt>Value_Type</tt> is the value type.</li> 35 36 <li><tt>Cmp_Fn</tt> is a value comparison functor</li> 37 38 <li><tt>Tag</tt> specifies which underlying data structure 39 to use.</li> 40 41 <li><tt>Allocator</tt> is an allocator 42 type.</li> 43 </ol> 44 45 <p>The <tt>Tag</tt> parameter specifies which underlying 46 data structure to use. Instantiating it by <a href= 47 "pairing_heap_tag.html"><tt>pairing_heap_tag</tt></a>, 48 <a href= 49 "binary_heap_tag.html"><tt>binary_heap_tag</tt></a>, 50 <a href= 51 "binomial_heap_tag.html"><tt>binomial_heap_tag</tt></a>, 52 <a href= 53 "rc_binomial_heap_tag.html"><tt>rc_binomial_heap_tag</tt></a>, 54 or <a href= 55 "thin_heap_tag.html"><tt>thin_heap_tag</tt></a>, 56 specifies, respectively, an underlying pairing heap [<a href= 57 "references.html#fredman86pairing">fredman86pairing</a>], 58 binary heap [<a href="references.html#clrs2001">clrs2001</a>], 59 binomial heap [<a href= 60 "references.html#clrs2001">clrs2001</a>], a binomial heap with 61 a redundant binary counter [<a href= 62 "references.html#maverik_lowerbounds">maverik_lowerbounds</a>], 63 or a thin heap [<a href= 64 "references.html#kt99fat_heaps">kt99fat_heas</a>]. These are 65 explained further in <a href="#pq_imp">Implementations</a>.</p> 66 67 <p>As mentioned in <a href= 68 "tutorial.html#pq">Tutorial::Priority Queues</a>, 69 <a href= 70 "priority_queue.html"><tt>__gnu_pbds::priority_queue</tt></a> 71 shares most of the same interface with <tt>std::priority_queue</tt>. 72 <i>E.g.</i> if <tt>q</tt> is a priority queue of type 73 <tt>Q</tt>, then <tt>q.top()</tt> will return the "largest" 74 value in the container (according to <tt><b>typename</b> 75 Q::cmp_fn</tt>). <a href= 76 "priority_queue.html"><tt>__gnu_pbds::priority_queue</tt></a> 77 has a larger (and very slightly different) interface than 78 <tt>std::priority_queue</tt>, however, since typically 79 <tt>push</tt> and <tt>pop</tt> are deemed insufficient for 80 manipulating priority-queues. </p> 81 82 <p>Different settings require different priority-queue 83 implementations which are described in <a href= 84 "#pq_imp">Implementations</a>; <a href="#pq_traits">Traits</a> 85 discusses ways to differentiate between the different traits of 86 different implementations.</p> 87 88 <h2><a name="pq_it" id="pq_it">Iterators</a></h2> 89 90 <p>There are many different underlying-data structures for 91 implementing priority queues. Unfortunately, most such 92 structures are oriented towards making <tt>push</tt> and 93 <tt>top</tt> efficient, and consequently don't allow efficient 94 access of other elements: for instance, they cannot support an efficient 95 <tt>find</tt> method. In the use case where it 96 is important to both access and "do something with" an 97 arbitrary value, one would be out of luck. For example, many graph algorithms require 98 modifying a value (typically increasing it in the sense of the 99 priority queue's comparison functor).</p> 100 101 <p>In order to access and manipulate an arbitrary value in a 102 priority queue, one needs to reference the internals of the 103 priority queue from some form of an associative container - 104 this is unavoidable. Of course, in order to maintain the 105 encapsulation of the priority queue, this needs to be done in a 106 way that minimizes exposure to implementation internals.</p> 107 108 <p>In <tt>pb_ds</tt> the priority queue's <tt>insert</tt> 109 method returns an iterator, which if valid can be used for subsequent <tt>modify</tt> and 110 <tt>erase</tt> operations. This both preserves the priority 111 queue's encapsulation, and allows accessing arbitrary values (since the 112 returned iterators from the <tt>push</tt> operation can be 113 stored in some form of associative container).</p> 114 115 <p>Priority queues' iterators present a problem regarding their 116 invalidation guarantees. One assumes that calling 117 <tt><b>operator</b>++</tt> on an iterator will associate it 118 with the "next" value. Priority-queues are 119 self-organizing: each operation changes what the "next" value 120 means. Consequently, it does not make sense that <tt>push</tt> 121 will return an iterator that can be incremented - this can have 122 no possible use. Also, as in the case of hash-based containers, 123 it is awkward to define if a subsequent <tt>push</tt> operation 124 invalidates a prior returned iterator: it invalidates it in the 125 sense that its "next" value is not related to what it 126 previously considered to be its "next" value. However, it might not 127 invalidate it, in the sense that it can be 128 de-referenced and used for <tt>modify</tt> and <tt>erase</tt> 129 operations.</p> 130 131 <p>Similarly to the case of the other unordered associative 132 containers, <tt>pb_ds</tt> uses a distinction between 133 point-type and range type iterators. A priority queue's <tt>iterator</tt> can always be 134 converted to a <tt>point_iterator</tt>, and a 135 <tt>const_iterator</tt> can always be converted to a 136 <tt>const_point_iterator</tt>.</p> 137 138 <p>The following snippet demonstrates manipulating an arbitrary 139 value:</p> 140 <pre> 141<i>// A priority queue of integers.</i> 142<a href= 143"priority_queue.html">priority_queue</a><<b>int</b>> p; 144 145<i>// Insert some values into the priority queue.</i> 146<a href= 147"priority_queue.html">priority_queue</a><<b>int</b>>::point_iterator it = p.push(0); 148 149p.push(1); 150p.push(2); 151 152<i>// Now modify a value.</i> 153p.modify(it, 3); 154 155assert(p.top() == 3); 156</pre> 157 158 <p>(<a href="pq_examples.html#xref">Priority Queue 159 Examples::Cross-Referencing</a> shows a more detailed 160 example.)</p> 161 162 <p>It should be noted that an alternative design could embed an 163 associative container in a priority queue. Could, but most probably should not. To begin with, it should be noted that one 164 could always encapsulate a priority queue and an associative 165 container mapping values to priority queue iterators with no 166 performance loss. One cannot, however, "un-encapsulate" a 167 priority queue embedding an associative container, which might 168 lead to performance loss. Assume, that one needs to 169 associate each value with some data unrelated to priority 170 queues. Then using <tt>pb_ds</tt>'s design, one could use an 171 associative container mapping each value to a pair consisting 172 of this data and a priority queue's iterator. Using the 173 embedded method would need to use two associative 174 containers. Similar problems might arise in cases where a value 175 can reside simultaneously in many priority queues.</p> 176 177 <h2><a name="pq_imp" id="pq_imp">Implementations</a></h2> 178 179 <p>There are three main implementations of priority queues: the 180 first employs a binary heap, typically one which uses a 181 sequence; the second uses a tree (or forest of trees), which is 182 typically less structured than an associative container's tree; 183 the third simply uses an associative container. These are 184 shown, respectively, in Figures <a href= 185 "#pq_different_underlying_dss">Underlying Priority-Queue 186 Data-Structures</a> A1 and A2, Figure <a href= 187 "#pq_different_underlying_dss">Underlying Priority-Queue 188 Data-Structures</a> B, and Figures <a href= 189 "#pq_different_underlying_dss">Underlying Priority-Queue 190 Data-Structures</a> C.</p> 191 192 <h6 class="c1"><a name="pq_different_underlying_dss" id= 193 "pq_different_underlying_dss"><img src= 194 "pq_different_underlying_dss.png" alt="no image" /></a></h6> 195 196 <h6 class="c1">Underlying Priority-Queue Data-Structures.</h6> 197 198 <p>Roughly speaking, any value that is both pushed and popped 199 from a priority queue must incur a logarithmic expense (in the 200 amortized sense). Any priority queue implementation that would 201 avoid this, would violate known bounds on comparison-based 202 sorting (see, <i>e.g.</i>, [<a href= 203 "references.html#clrs2001">clrs2001</a>] and <a href= 204 "references.html#brodal96priority">brodal96priority</a>]).</p> 205 206 <p>Most implementations do 207 not differ in the asymptotic amortized complexity of 208 <tt>push</tt> and <tt>pop</tt> operations, but they differ in 209 the constants involved, in the complexity of other operations 210 (<i>e.g.</i>, <tt>modify</tt>), and in the worst-case 211 complexity of single operations. In general, the more 212 "structured" an implementation (<i>i.e.</i>, the more internal 213 invariants it possesses) - the higher its amortized complexity 214 of <tt>push</tt> and <tt>pop</tt> operations.</p> 215 216 <p><tt>pb_ds</tt> implements different algorithms using a 217 single class: <a href="priority_queue.html">priority_queue</a>. 218 Instantiating the <tt>Tag</tt> template parameter, "selects" 219 the implementation:</p> 220 221 <ol> 222 <li>Instantiating <tt>Tag = <a href= 223 "binary_heap_tag.html">binary_heap_tag</a></tt> creates 224 a binary heap of the form in Figures <a href= 225 "#pq_different_underlying_dss">Underlying Priority-Queue 226 Data-Structures</a> A1 or A2. The former is internally 227 selected by <a href="priority_queue.html">priority_queue</a> 228 if <tt>Value_Type</tt> is instantiated by a primitive type 229 (<i>e.g.</i>, an <tt><b>int</b></tt>); the latter is 230 internally selected for all other types (<i>e.g.</i>, 231 <tt>std::string</tt>). This implementations is relatively 232 unstructured, and so has good <tt>push</tt> and <tt>pop</tt> 233 performance; it is the "best-in-kind" for primitive 234 types, <i>e.g.</i>, <tt><b>int</b></tt>s. Conversely, it has 235 high worst-case performance, and can support only linear-time 236 <tt>modify</tt> and <tt>erase</tt> operations; this is 237 explained further in <a href="#pq_traits">Traits</a>.</li> 238 239 <li>Instantiating <tt>Tag = <a href= 240 "pairing_heap_tag.html">pairing_heap_tag</a></tt> 241 creates a pairing heap of the form in Figure <a href= 242 "#pq_different_underlying_dss">Underlying Priority-Queue 243 Data-Structures</a> B. This implementations too is relatively 244 unstructured, and so has good <tt>push</tt> and <tt>pop</tt> 245 performance; it is the "best-in-kind" for non-primitive 246 types, <i>e.g.</i>, <tt>std:string</tt>s. It also has very 247 good worst-case <tt>push</tt> and <tt>join</tt> performance 248 (<i>O(1)</i>), but has high worst-case <tt>pop</tt> 249 complexity.</li> 250 251 <li>Instantiating <tt>Tag = <a href= 252 "binomial_heap_tag.html">binomial_heap_tag</a></tt> 253 creates a binomial heap of the form in Figure <a href= 254 "#pq_different_underlying_dss">Underlying Priority-Queue 255 Data-Structures</a> B. This implementations is more 256 structured than a pairing heap, and so has worse 257 <tt>push</tt> and <tt>pop</tt> performance. Conversely, it 258 has sub-linear worst-case bounds for <tt>pop</tt>, 259 <i>e.g.</i>, and so it might be preferred in cases where 260 responsiveness is important.</li> 261 262 <li>Instantiating <tt>Tag = <a href= 263 "rc_binomial_heap_tag.html">rc_binomial_heap_tag</a></tt> 264 creates a binomial heap of the form in Figure <a href= 265 "#pq_different_underlying_dss">Underlying Priority-Queue 266 Data-Structures</a> B, accompanied by a redundant counter 267 which governs the trees. This implementations is therefore 268 more structured than a binomial heap, and so has worse 269 <tt>push</tt> and <tt>pop</tt> performance. Conversely, it 270 guarantees <i>O(1)</i> <tt>push</tt> complexity, and so it 271 might be preferred in cases where the responsiveness of a 272 binomial heap is insufficient.</li> 273 274 <li>Instantiating <tt>Tag = <a href= 275 "thin_heap_tag.html">thin_heap_tag</a></tt> creates a 276 thin heap of the form in Figure <a href= 277 "#pq_different_underlying_dss">Underlying Priority-Queue 278 Data-Structures</a> B. This implementations too is more 279 structured than a pairing heap, and so has worse 280 <tt>push</tt> and <tt>pop</tt> performance. Conversely, it 281 has better worst-case and identical amortized complexities 282 than a Fibonacci heap, and so might be more appropriate for 283 some graph algorithms.</li> 284 </ol> 285 286 <p><a href="pq_performance_tests.html">Priority-Queue 287 Performance Tests</a> shows some results for the above, and 288 discusses these points further.</p> 289 290 <p>Of course, one can use any order-preserving associative 291 container as a priority queue, as in Figure <a href= 292 "#pq_different_underlying_dss">Underlying Priority-Queue 293 Data-Structures</a> C, possibly by creating an adapter class 294 over the associative container (much as 295 <tt>std::priority_queue</tt> can adapt <tt>std::vector</tt>). 296 This has the advantage that no cross-referencing is necessary 297 at all; the priority queue itself is an associative container. 298 Most associative containers are too structured to compete with 299 priority queues in terms of <tt>push</tt> and <tt>pop</tt> 300 performance.</p> 301 302 <h2><a name="pq_traits" id="pq_traits">Traits</a></h2> 303 304 <p>It would be nice if all priority queues could 305 share exactly the same behavior regardless of implementation. Sadly, this is not possible. Just one for instance is in join operations: joining 306 two binary heaps might throw an exception (not corrupt 307 any of the heaps on which it operates), but joining two pairing 308 heaps is exception free.</p> 309 310 <p>Tags and traits are very useful for manipulating generic 311 types. <a href= 312 "priority_queue.html"><tt>__gnu_pbds::priority_queue</tt></a> 313 publicly defines <tt>container_category</tt> as one of the tags 314 discussed in <a href="#pq_imp">Implementations</a>. Given any 315 container <tt>Cntnr</tt>, the tag of the underlying 316 data structure can be found via <tt><b>typename</b> 317 Cntnr::container_category</tt>; this is one of the types shown in 318 Figure <a href="#pq_tag_cd">Data-structure tag class 319 hierarchy</a>.</p> 320 321 <h6 class="c1"><a name="pq_tag_cd" id= 322 "pq_tag_cd"><img src="priority_queue_tag_cd.png" alt= 323 "no image" /></a></h6> 324 325 <h6 class="c1">Data-structure tag class hierarchy.</h6> 326 327 <p>Additionally, a traits mechanism can be used to query a 328 container type for its attributes. Given any container 329 <tt>Cntnr</tt>, then <tt><a href= 330 "assoc_container_traits.html">__gnu_pbds::container_traits</a><Cntnr></tt> 331 is a traits class identifying the properties of the 332 container.</p> 333 334 <p>To find if a container might throw if two of its objects are 335 joined, one can use <a href= 336 "assoc_container_traits.html"><tt>container_traits</tt></a><tt><Cntnr>::split_join_can_throw</tt>, 337 for example.</p> 338 339 <p>Different priority-queue implementations have different invalidation guarantees. This is 340 especially important, since as explained in <a href= 341 "#pq_it">Iterators</a>, there is no way to access an arbitrary 342 value of priority queues except for iterators. Similarly to 343 associative containers, one can use 344 <a href= 345 "assoc_container_traits.html"><tt>container_traits</tt></a><tt><Cntnr>::invalidation_guarantee</tt> 346 to get the invalidation guarantee type of a priority queue.</p> 347 348 <p>It is easy to understand from Figure <a href= 349 "#pq_different_underlying_dss">Underlying Priority-Queue 350 Data-Structures</a>, what <a href= 351 "assoc_container_traits.html"><tt>container_traits</tt></a><tt><Cntnr>::invalidation_guarantee</tt> 352 will be for different implementations. All implementations of 353 type <a href="#pq_different_underlying_dss">Underlying 354 Priority-Queue Data-Structures</a> B have <a href= 355 "point_invalidation_guarantee.html"><tt>point_invalidation_guarantee</tt></a>: 356 the container can freely internally reorganize the nodes - 357 range-type iterators are invalidated, but point-type iterators 358 are always valid. Implementations of type <a href= 359 "#pq_different_underlying_dss">Underlying Priority-Queue 360 Data-Structures</a> A1 and A2 have <a href= 361 "basic_invalidation_guarantee.html"><tt>basic_invalidation_guarantee</tt></a>: 362 the container can freely internally reallocate the array - both 363 point-type and range-type iterators might be invalidated.</p> 364 365 <p>This has major implications, and constitutes a good reason to avoid 366 using binary heaps. A binary heap can perform <tt>modify</tt> 367 or <tt>erase</tt> efficiently <u>given a valid point-type 368 iterator</u>. However, inn order to supply it with a valid point-type 369 iterator, one needs to iterate (linearly) over all 370 values, then supply the relevant iterator (recall that a 371 range-type iterator can always be converted to a point-type 372 iterator). This means that if the number of <tt>modify</tt> or 373 <tt>erase</tt> operations is non-negligible (say 374 super-logarithmic in the total sequence of operations) - binary 375 heaps will perform badly.</p> 376 <pre> 377 378</pre> 379 </div> 380</body> 381</html> 382