Skip to main content

10.6 Priority Queue

Priority queue allows retrieving the maximum value in O(1)O(1) time and inserting or removing the maximum value in O(logn)O(\log n) time.

Figure 10.2: (Max) Heap, maintaining the "greater than" relationship in the data structure

Priority queues are often implemented using heaps. A heap is a complete binary tree in which each node's value is always greater than or equal to its child nodes. When implementing heaps, arrays are often used instead of pointers to build a tree. This is because heaps are complete binary trees, so in an array representation, the parent node of position ii is located at (i1)/2(i-1)/2, and its two child nodes are located at 2i+12i+1 and 2i+22i+2, respectively.

Here is the implementation of heaps. The two core operations are "swim" and "sink": if a node is greater than its parent node, we swap them; after swapping, it may still be greater than its new parent node, so we continue comparing and swapping, known as "swim." Similarly, if a node is smaller than its parent, it needs to compare and swap downward continuously, known as "sink." If a node has two child nodes, we always swap with the largest child node.

class Heap {
public:
Heap() {}
// Swim
void swim(int pos) {
int next_pos = (pos - 1) / 2;
while (pos > 0 && heap_[next_pos] < heap_[pos]) {
swap(heap_[next_pos], heap_[pos]);
pos = next_pos;
next_pos = (pos - 1) / 2;
}
}
// Sink
void sink(int pos) {
int n = heap_.size();
int next_pos = 2 * pos + 1;
while (next_pos < n) {
if (next_pos < n - 1 && heap_[next_pos] < heap_[next_pos + 1]) {
++next_pos;
}
if (heap_[pos] >= heap_[next_pos]) {
break;
}
swap(heap_[next_pos], heap_[pos]);
pos = next_pos;
next_pos = 2 * pos + 1;
}
}
// Insert any value: place the new number at the last position, then swim.
void push(int k) {
heap_.push_back(k);
swim(heap_.size() - 1);
}
// Delete the maximum value: move the last number to the front, then sink.
void pop() {
heap_[0] = heap_.back();
heap_.pop_back();
sink(0);
}
// Retrieve the maximum value.
int top() { return heap_[0]; }

private:
vector<int> heap_;
};

By swapping the greater-than and less-than operators in the algorithm, we can also create a priority queue that retrieves the minimum value quickly.

23. Merge k Sorted Lists

Problem Description

Given k sorted linked lists, try to merge them into one sorted linked list.

Input and Output Example

The input is a one-dimensional array, where each position stores the head node of a linked list; the output is a single linked list.

Input:
[1->4->5,
1->3->4,
2->6]
Output: 1->1->2->3->4->4->5->6

Solution Explanation

There are multiple ways to solve this problem, such as using a merge sort-like approach to merge the lists pair by pair. Here, we demonstrate a faster method: store all the linked lists in a priority queue and extract the node with the smallest value from the head of all lists at each step until all lists have been completely merged.

Since the default comparison function of a C++ priority_queue is for a max-heap and maintains an increasing order, to get the smallest node values, we need to implement a min-heap. Thus, the comparison function for the heap should maintain a decreasing order, i.e., the lambda function should use the greater-than operator instead of the less-than operator used for increasing order.

ListNode* mergeKLists(vector<ListNode*>& lists) {
auto comp = [](ListNode* l1, ListNode* l2) { return l1->val > l2->val; };
priority_queue<ListNode*, vector<ListNode*>, decltype(comp)> pq;
for (ListNode* l : lists) {
if (l) {
pq.push(l);
}
}
ListNode *dummy = new ListNode(0), *cur = dummy;
while (!pq.empty()) {
cur->next = pq.top();
pq.pop();
cur = cur->next;
if (cur->next) {
pq.push(cur->next);
}
}
return dummy->next;
}

218. The Skyline Problem

Problem Description

Given the start and end positions along with the height of buildings, return the critical points of the building's silhouette (skyline).

Input and Output Example

The input is a 2D integer array representing each building as [left, right, height]; the output is a 2D integer array representing the x and y coordinates of the critical points of the skyline.

Figure 10.3: Problem 218 - Example of buildings and their skyline
Input: [[2 9 10], [3 7 15], [5 12 12], [15 20 10], [19 24 8]]
Output: [[2 10], [3 15], [7 12], [12 0], [15 10], [20 8], [24, 0]]

Solution Explanation

We can use a priority queue to store the height and the right endpoint of each building (using a pair). This helps us identify the next building that raises the skyline and interferes with the previous building's endpoint.

Since Python's heapq implements a min-heap, we store negative values for heights to simulate a max-heap.

This problem is relatively complex. If you find it challenging to grasp, consider skipping it for now or illustrating examples on paper for better understanding.

vector<vector<int>> getSkyline(vector<vector<int>>& buildings) {
vector<vector<int>> skyline;
priority_queue<pair<int, int>> pq; // <height, right>
int i = 0, n = buildings.size();
int cur_x, cur_h;
while (i < n || !pq.empty()) {
if (pq.empty() || (i < n && buildings[i][0] <= pq.top().second)) {
cur_x = buildings[i][0];
while (i < n && cur_x == buildings[i][0]) {
pq.emplace(buildings[i][2], buildings[i][1]);
++i;
}
} else {
cur_x = pq.top().second;
while (!pq.empty() && cur_x >= pq.top().second) {
pq.pop();
}
}
cur_h = pq.empty() ? 0 : pq.top().first;
if (skyline.empty() || cur_h != skyline.back()[1]) {
skyline.push_back({cur_x, cur_h});
}
}
return skyline;
}