-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpriority_queue.cpp
More file actions
306 lines (244 loc) · 8.23 KB
/
priority_queue.cpp
File metadata and controls
306 lines (244 loc) · 8.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
/*
Priority queue implementation using a binary heap.
This module provides a generic priority queue that supports adding items with priorities,
updating priorities, removing items, and popping the item with the lowest priority.
The implementation uses C++ std::priority_queue for efficient heap operations.
Standard library alternatives:
- C++: std::priority_queue (basic operations only, no key-based updates/removal)
- Python: heapq module (min-heap only, no key-based updates/removal)
- Java: PriorityQueue class (basic operations only, no key-based updates/removal)
Time complexity: O(log n) for add/update and pop operations, O(log n) for remove.
Space complexity: O(n) where n is the number of items in the queue.
*/
#include <cassert>
#include <functional>
#include <iostream>
#include <queue>
#include <stdexcept>
#include <unordered_map>
#include <utility>
template <typename KeyT, typename PriorityT>
class PriorityQueue {
private:
struct Entry {
PriorityT priority;
KeyT key;
size_t version;
Entry(PriorityT p, const KeyT& k, size_t v) : priority(p), key(k), version(v) {}
// For min-heap behavior (lowest priority first)
bool operator>(const Entry& other) const {
return priority > other.priority;
}
};
std::priority_queue<Entry, std::vector<Entry>, std::greater<Entry>> pq;
std::unordered_map<KeyT, size_t> key_versions; // Maps keys to their current version
size_t next_version;
int size_count;
public:
PriorityQueue() : next_version(0), size_count(0) {}
void set(const KeyT& key, const PriorityT& priority) {
// Add a new task or update the priority of an existing task
if (key_versions.find(key) != key_versions.end()) {
// Key exists, this will invalidate the old entry
size_count--; // We'll increment it back below
}
size_count++;
size_t version = next_version++;
key_versions[key] = version;
pq.push(Entry(priority, key, version));
}
void remove(const KeyT& key) {
// Mark an existing task as removed by updating its version
auto it = key_versions.find(key);
if (it == key_versions.end()) {
throw std::runtime_error("Key not found in priority queue");
}
key_versions.erase(it);
size_count--;
}
std::pair<KeyT, PriorityT> pop() {
// Remove and return the lowest priority task. Throw exception if empty.
while (!pq.empty()) {
Entry top = pq.top();
pq.pop();
// Check if this entry is still valid (not removed/updated)
auto it = key_versions.find(top.key);
if (it != key_versions.end() && it->second == top.version) {
key_versions.erase(it);
size_count--;
return std::make_pair(top.key, top.priority);
}
}
throw std::runtime_error("pop from an empty priority queue");
}
std::pair<KeyT, PriorityT> peek() {
// Return the lowest priority task without removing. Returns empty result throws if empty.
while (!pq.empty()) {
Entry top = pq.top();
// Check if this entry is still valid (not removed/updated)
auto it = key_versions.find(top.key);
if (it != key_versions.end() && it->second == top.version) {
return std::make_pair(top.key, top.priority);
}
// Remove the invalid entry from the top
pq.pop();
}
throw std::runtime_error("peek from an empty priority queue");
}
bool contains(const KeyT& key) const {
return key_versions.find(key) != key_versions.end();
}
int size() const {
return size_count;
}
bool empty() const {
return size_count == 0;
}
};
void test_main() {
PriorityQueue<std::string, int> p;
p.set("x", 15);
p.set("y", 23);
p.set("z", 8);
auto peek_result = p.peek();
assert(peek_result.first == "z" && peek_result.second == 8);
auto result1 = p.pop();
assert(result1.first == "z" && result1.second == 8);
auto result2 = p.pop();
assert(result2.first == "x" && result2.second == 15);
}
// Don't write tests below during competition.
void test_basic_operations() {
PriorityQueue<std::string, int> pq;
// Test empty queue
assert(pq.empty());
assert(pq.size() == 0);
// Add items
pq.set("task1", 10);
pq.set("task2", 5);
pq.set("task3", 15);
assert(pq.size() == 3);
auto peek_result = pq.peek();
assert(peek_result.first == "task2" && peek_result.second == 5);
// Pop in priority order
auto item1 = pq.pop();
assert(item1.first == "task2" && item1.second == 5);
assert(pq.size() == 2);
auto item2 = pq.pop();
assert(item2.first == "task1" && item2.second == 10);
auto item3 = pq.pop();
assert(item3.first == "task3" && item3.second == 15);
assert(pq.size() == 0);
}
void test_update_priority() {
PriorityQueue<std::string, int> pq;
pq.set("task1", 10);
pq.set("task2", 5);
// Update task1 to have higher priority
pq.set("task1", 3);
assert(pq.peek().first == "task1");
assert(pq.peek().second == 3);
assert(pq.size() == 2);
// Pop should now give task1 first
auto item1 = pq.pop();
assert(item1.first == "task1" && item1.second == 3);
auto item2 = pq.pop();
assert(item2.first == "task2" && item2.second == 5);
}
void test_remove() {
PriorityQueue<std::string, int> pq;
pq.set("task1", 10);
pq.set("task2", 5);
pq.set("task3", 15);
// Remove middle priority task
pq.remove("task1");
assert(pq.size() == 2);
assert(!pq.contains("task1"));
// Verify correct items remain
auto item1 = pq.pop();
assert(item1.first == "task2" && item1.second == 5);
auto item2 = pq.pop();
assert(item2.first == "task3" && item2.second == 15);
}
void test_contains() {
PriorityQueue<std::string, int> pq;
pq.set("task1", 10);
pq.set("task2", 5);
assert(pq.contains("task1"));
assert(pq.contains("task2"));
assert(!pq.contains("task3"));
pq.remove("task1");
assert(!pq.contains("task1"));
}
void test_empty_operations() {
PriorityQueue<std::string, int> pq;
// Test pop on empty queue
bool caught = false;
try {
pq.pop();
} catch (const std::runtime_error&) { caught = true; }
assert(caught);
// Test peek on empty queue
caught = false;
try {
pq.peek();
} catch (const std::runtime_error&) { caught = true; }
assert(caught);
}
void test_remove_nonexistent() {
PriorityQueue<std::string, int> pq;
pq.set("task1", 10);
bool caught = false;
try {
pq.remove("nonexistent");
} catch (const std::runtime_error&) { caught = true; }
assert(caught);
}
void test_single_element() {
PriorityQueue<std::string, int> pq;
pq.set("only", 42);
assert(pq.size() == 1);
assert(pq.peek().first == "only" && pq.peek().second == 42);
assert(pq.pop().first == "only");
assert(pq.size() == 0);
}
void test_duplicate_priorities() {
PriorityQueue<std::string, int> pq;
pq.set("task1", 10);
pq.set("task2", 10);
pq.set("task3", 10);
assert(pq.size() == 3);
// All should pop eventually
std::vector<std::pair<std::string, int>> results;
results.push_back(pq.pop());
results.push_back(pq.pop());
results.push_back(pq.pop());
assert(results.size() == 3);
for (const auto& [key, priority] : results) { assert(priority == 10); }
}
void test_with_floats() {
PriorityQueue<std::string, double> pq;
pq.set("a", 1.5);
pq.set("b", 0.5);
pq.set("c", 2.3);
auto item1 = pq.pop();
assert(item1.first == "b" && item1.second == 0.5);
auto item2 = pq.pop();
assert(item2.first == "a" && item2.second == 1.5);
auto item3 = pq.pop();
assert(item3.first == "c" && item3.second == 2.3);
}
int main() {
test_basic_operations();
test_update_priority();
test_remove();
test_contains();
test_empty_operations();
test_remove_nonexistent();
test_single_element();
test_duplicate_priorities();
test_with_floats();
test_main();
std::cout << "All Priority Queue tests passed!" << std::endl;
return 0;
}