AI 24/25 Project Software
Documentation for the AI 24/25 course programming project software
Loading...
Searching...
No Matches
priority_queues.h
1#ifndef ALGORITHMS_PRIORITY_QUEUES_H
2#define ALGORITHMS_PRIORITY_QUEUES_H
3
4#include "downward/utils/collections.h"
5
6#include <cassert>
7#include <iostream>
8#include <queue>
9#include <utility>
10#include <vector>
11
12/*
13 We define three priority queue classes here: HeapQueue (heap-based),
14 BucketQueue (bucket-based), and AdaptiveQueue (starts out bucket-based,
15 transforms into heap-based if that seems to make sense).
16
17 More precisely, an AdaptiveQueue is converted from a BucketQueue to
18 a HeapQueue when the number of required buckets exceeds both
19 BucketQueue::MIN_BUCKETS_BEFORE_SWITCH and the total number of
20 pushes to the queue since it was last clear()ed or constructed.
21
22 Note: AdaptiveQueue does not derive from AbstractQueue since this is
23 currently not necessary, and by not deriving we can save virtual
24 function calls and do some additional inlining. The class has the
25 same interface as AbstractQueue, however, to facilitate swapping the
26 different implementations in and out.
27 */
28namespace priority_queues {
29template <typename Key, typename Value>
30class AbstractQueue {
31public:
32 typedef std::pair<Key, Value> Entry;
33
34 AbstractQueue() {}
35 virtual ~AbstractQueue() {}
36
37 virtual void push(const Key& key, const Value& value) = 0;
38 virtual Entry pop() = 0;
39 virtual bool empty() const = 0;
40 virtual void clear() = 0;
41
42 virtual AbstractQueue<Key, Value>* convert_if_necessary(const Key&)
43 {
44 /* Determine if this queue would still offer adequate
45 performance after pushing another element with the given
46 key.
47
48 If yes, return this queue.
49
50 If not, return a new queue (presumably of a different type)
51 that appears more adequate, initialize it with your
52 elements (possibly destructively, i.e., clearing this queue
53 at the same time), and return the new queue.
54 */
55 return this;
56 }
57
58 /* The following method is a bit of a hack to influence the
59 conversion strategy to convert to the heap-based queue less
60 aggressively in the incremental LM-cut configurations. A better
61 solution would be to use a priority queue that is generally
62 more performant, or a better conversion strategy, but both of
63 these would require much more experimentation. */
64
65 virtual void add_virtual_pushes(int num_extra_pushes) = 0;
66};
67
68template <typename Key, typename Value>
69class HeapQueue : public AbstractQueue<Key, Value> {
70 typedef typename AbstractQueue<Key, Value>::Entry Entry;
71
72 struct compare_func {
73 bool operator()(const Entry& lhs, const Entry& rhs) const
74 {
75 return lhs.first > rhs.first;
76 }
77 };
78
79 class Heap
80 : public std::priority_queue<Entry, std::vector<Entry>, compare_func> {
81 // We inherit since our friend needs access to the underlying
82 // container c which is a protected member.
83 friend class HeapQueue;
84 };
85
86 Heap heap;
87
88public:
89 HeapQueue() {}
90
91 virtual ~HeapQueue() {}
92
93 virtual void push(const Key& key, const Value& value)
94 {
95 heap.push(std::make_pair(key, value));
96 }
97
98 virtual Entry pop()
99 {
100 assert(!heap.empty());
101 Entry result = heap.top();
102 heap.pop();
103 return result;
104 }
105
106 virtual bool empty() const { return heap.empty(); }
107
108 virtual void clear() { heap.c.clear(); }
109
110 static HeapQueue<Key, Value>*
111 create_from_sorted_entries_destructively(std::vector<Entry>& entries)
112 {
113 // Create a new heap from the entries, which must be sorted.
114 // The passed-in vector is cleared as a side effect.
115 HeapQueue<Key, Value>* result = new HeapQueue<Key, Value>;
116 result->heap.c.swap(entries);
117 // Since the entries are sorted, we do not need to heapify.
118 return result;
119 }
120
121 virtual void add_virtual_pushes(int /*num_extra_pushes*/) {}
122};
123
124template <typename Value>
125class BucketQueue : public AbstractQueue<int, Value> {
126 static const int MIN_BUCKETS_BEFORE_SWITCH = 100;
127 static const bool DEBUG = false;
128
129 typedef typename AbstractQueue<int, Value>::Entry Entry;
130
131 typedef std::vector<Value> Bucket;
132 std::vector<Bucket> buckets;
133 mutable int current_bucket_no;
134 int num_entries;
135 int num_pushes;
136
137 void update_current_bucket_no() const
138 {
139 int num_buckets = buckets.size();
140 while (current_bucket_no < num_buckets &&
141 buckets[current_bucket_no].empty())
142 ++current_bucket_no;
143 }
144
145 void extract_sorted_entries(std::vector<Entry>& result)
146 {
147 // Generate vector with the entries of the queue in sorted
148 // order, removing them from this queue as a side effect.
149 assert(result.empty());
150 result.reserve(num_entries);
151 for (int key = current_bucket_no; num_entries != 0; ++key) {
152 Bucket& bucket = buckets[key];
153 for (size_t i = 0; i < bucket.size(); ++i)
154 result.push_back(std::make_pair(key, bucket[i]));
155 num_entries -= bucket.size();
156 Bucket empty_bucket;
157 bucket.swap(empty_bucket);
158 }
159 current_bucket_no = 0;
160 }
161
162public:
163 BucketQueue()
164 : current_bucket_no(0)
165 , num_entries(0)
166 , num_pushes(0)
167 {
168 }
169
170 virtual ~BucketQueue() {}
171
172 virtual void push(const int& key, const Value& value)
173 {
174 ++num_entries;
175 ++num_pushes;
176 assert(num_pushes > 0); // Check against overflow.
177 int num_buckets = buckets.size();
178 if (key >= num_buckets)
179 buckets.resize(key + 1);
180 else if (key < current_bucket_no)
181 current_bucket_no = key;
182 buckets[key].push_back(value);
183 }
184
185 virtual Entry pop()
186 {
187 assert(num_entries > 0);
188 --num_entries;
189 update_current_bucket_no();
190 Bucket& current_bucket = buckets[current_bucket_no];
191 Value top_element = current_bucket.back();
192 current_bucket.pop_back();
193 return std::make_pair(current_bucket_no, top_element);
194 }
195
196 virtual bool empty() const { return num_entries == 0; }
197
198 virtual void clear()
199 {
200 for (int i = current_bucket_no; num_entries != 0; ++i) {
201 assert(utils::in_bounds(i, buckets));
202 int bucket_size = buckets[i].size();
203 assert(bucket_size <= num_entries);
204 num_entries -= bucket_size;
205 buckets[i].clear();
206 }
207 current_bucket_no = 0;
208 assert(num_entries == 0);
209 num_pushes = 0;
210 }
211
212 virtual AbstractQueue<int, Value>* convert_if_necessary(const int& key)
213 {
214 if (key >= MIN_BUCKETS_BEFORE_SWITCH && key > num_pushes) {
215 if (DEBUG) {
216 std::cout << "Switch from bucket-based to heap-based queue "
217 << "at key = " << key
218 << ", num_pushes = " << num_pushes << std::endl;
219 }
220 std::vector<Entry> entries;
221 extract_sorted_entries(entries);
222 return HeapQueue<int, Value>::
223 create_from_sorted_entries_destructively(entries);
224 }
225 return this;
226 }
227
228 virtual void add_virtual_pushes(int num_extra_pushes)
229 {
230 num_pushes += num_extra_pushes;
231 }
232};
233
234template <typename Value>
235class AdaptiveQueue {
236 AbstractQueue<int, Value>* wrapped_queue;
237 // Forbid assigning or copying -- would need to implement them properly.
238 AdaptiveQueue& operator=(const AdaptiveQueue<Value>&);
239 AdaptiveQueue(const AdaptiveQueue<Value>&);
240
241public:
242 typedef std::pair<int, Value> Entry;
243
244 AdaptiveQueue()
245 : wrapped_queue(new BucketQueue<Value>)
246 {
247 }
248
249 ~AdaptiveQueue() { delete wrapped_queue; }
250
251 void push(int key, const Value& value)
252 {
253 AbstractQueue<int, Value>* q = wrapped_queue->convert_if_necessary(key);
254 if (q != wrapped_queue) {
255 delete wrapped_queue;
256 wrapped_queue = q;
257 }
258 wrapped_queue->push(key, value);
259 }
260
261 Entry pop() { return wrapped_queue->pop(); }
262
263 bool empty() const { return wrapped_queue->empty(); }
264
265 void clear() { wrapped_queue->clear(); }
266
267 void add_virtual_pushes(int num_extra_pushes)
268 {
269 wrapped_queue->add_virtual_pushes(num_extra_pushes);
270 }
271};
272} // namespace priority_queues
273
274#endif