✅ 优先队列 priority_queue
更多关于优先队列的知识,请跳转至:https://en.oi-wiki.org/lang/pb-ds/pq/
大顶堆:priority_queue<int> pq
小顶堆:priority_queue<int, vector<int>, greater<>> pq
相关例题 :
__gnu_pbds :: priority_queue
附: 官方文档地址——复杂度及常数测试
1 2 3 #include <ext/pb_ds/priority_queue.hpp> using namespace __gnu_pbds;__gnu_pbds ::priority_queue<T, Compare, Tag, Allocator>
模板形参 :
T
: 储存的元素类型
Compare
: 提供严格的弱序比较类型
Tag
是 __gnu_pbds
提供的不同的五种堆,Tag
参数默认是 pairing_heap_tag
,这五种分别是:
pairing_heap_tag
:配对堆,官方文档认为在非原生元素(如自定义结构体/ std :: string
/ pair
) 中,配对堆表现最好
binary_heap_tag
:二叉堆,官方文档认为在原生元素中二叉堆表现最好,不过我测试的表现并没有那么好
binomial_heap_tag
:二项堆,二项堆在合并操作的表现要优于配对堆*但是其取堆顶元素的
rc_binomial_heap_tag
:冗余计数二项堆
thin_heap_tag
:除了合并的复杂度都和 Fibonacci 堆一样的一个 tag
Allocator
:空间配置器,由于 OI 中很少出现,故这里不做讲解
由于本篇文章只是提供给学习算法竞赛的同学们,故对于后四个 tag 只会简单的介绍复杂度,第一个会介绍成员函数和使用方法。
经作者本机测试堆的基础操作,结合 GNU 官方的复杂度测试,Dijkstra 测试,都表明:至少对于 OIer 来讲,除了配对堆(即默认 tag)的其他四个 tag 都是鸡肋,要么没用,要么常数大到不如 std
的,且有可能造成 MLE
,故这里只推荐用默认的配对堆。同样,配对堆也优于 algorithm
库中的 make_heap()
。
构造方式 要注明命名空间因为和 std
的类名称重复。
1 2 3 4 5 __gnu_pbds ::priority_queue<int > __gnu_pbds::priority_queue<int , greater<int > > __gnu_pbds ::priority_queue<int , greater<int >, pairing_heap_tag> __gnu_pbds ::priority_queue<int >::point_iterator id; id = q.push (1 );
成员函数
push()
: 向堆中压入一个元素,返回该元素位置的迭代器。
pop()
: 将堆顶元素弹出。
top()
: 返回堆顶元素。
size()
返回元素个数。
empty()
返回是否非空。
modify(point_iterator, const key)
: 把迭代器位置的 key
修改为传入的 key
,并对底层储存结构进行排序。
erase(point_iterator)
: 把迭代器位置的键值从堆中擦除。
join(__gnu_pbds :: priority_queue &other)
: 把 other
合并到 *this
并把 other
清空。
时间复杂度 使用的 tag 决定了每个操作的时间复杂度:
239. 滑动窗口最大值 给你一个整数数组 nums
,有一个大小为 k
的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k
个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值 。
1 2 3 4 5 6 7 8 9 10 11 输入:nums = [1 ,3 ,-1 ,-3 ,5 ,3 ,6 ,7 ], k = 3 输出:[3 ,3 ,5 ,5 ,6 ,7 ] 解释: 滑动窗口的位置 最大值 --------------- ----- [1 3 -1 ] -3 5 3 6 7 3 1 [3 -1 -3 ] 5 3 6 7 3 1 3 [-1 -3 5 ] 3 6 7 5 1 3 -1 [-3 5 3 ] 6 7 5 1 3 -1 -3 [5 3 6 ] 7 6 1 3 -1 -3 5 [3 6 7 ] 7
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : vector<int > maxSlidingWindow (vector<int >& nums, int k) { priority_queue<pair<int , int >, vector<pair<int , int >>, less<>> pq; for (int i = 0 ; i < k; i++) { pq.emplace (nums[i], i); } vector<int > ans{pq.top ().first}; for (int i = k; i < nums.size (); i++) { pq.emplace (nums[i], i); while (pq.top ().second <= i - k) { pq.pop (); } ans.push_back (pq.top ().first); } return ans; } };
295. 数据流的中位数 中位数 是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。
例如 arr = [2,3,4]
的中位数是 3
。
例如 arr = [2,3]
的中位数是 (2 + 3) / 2 = 2.5
。
实现 MedianFinder 类:
MedianFinder()
初始化 MedianFinder
对象。
void addNum(int num)
将数据流中的整数 num
添加到数据结构中。
double findMedian()
返回到目前为止所有元素的中位数。与实际答案相差 10-5
以内的答案将被接受。
1 2 3 4 5 6 7 8 9 10 11 12 13 输入 ["MedianFinder" , "addNum" , "addNum" , "findMedian" , "addNum" , "findMedian" ] [[], [1 ], [2 ], [], [3 ], []] 输出 [null, null, null, 1.5 , null, 2.0 ] 解释 MedianFinder medianFinder = new MedianFinder (); medianFinder.addNum (1 ); medianFinder.addNum (2 ); medianFinder.findMedian (); medianFinder.addNum (3 ); medianFinder.findMedian ();
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 class MedianFinder {private : priority_queue<int > left; priority_queue<int , vector<int >, greater<>> right; public : MedianFinder () {} void addNum (int num) { if (left.size () == right.size ()) { right.push (num); left.push (right.top ()); right.pop (); } else { left.push (num); right.push (left.top ()); left.pop (); } } double findMedian () { return (left.size () + right.size ()) % 2 ? left.top () : static_cast <double >(left.top () + right.top ()) / 2 ; } };
480. 滑动窗口中位数 中位数是有序序列最中间的那个数。如果序列的长度是偶数,则没有最中间的数;此时中位数是最中间的两个数的平均数。
例如:
[2,3,4]
,中位数是 3
[2,3]
,中位数是 (2 + 3) / 2 = 2.5
给你一个数组 nums ,有一个长度为 k 的窗口从最左端滑动到最右端。窗口中有 k 个数,每次窗口向右移动 1 位。你的任务是找出每次窗口移动后得到的新窗口中元素的中位数,并输出由它们组成的数组。
给出 nums = [1,3,-1,-3,5,3,6,7]
,以及 k = 3。
1 2 3 4 5 6 7 8 窗口位置 中位数 --------------- ----- [1 3 -1 ] -3 5 3 6 7 1 1 [3 -1 -3 ] 5 3 6 7 -1 1 3 [-1 -3 5 ] 3 6 7 -1 1 3 -1 [-3 5 3 ] 6 7 3 1 3 -1 -3 [5 3 6 ] 7 5 1 3 -1 -3 5 [3 6 7 ] 6
因此,返回该滑动窗口的中位数数组 [1,-1,-1,3,5,6]
。
1️⃣ 哈希表 + 逻辑删除 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 class Solution {public : priority_queue<int > left; priority_queue<int , vector<int >, greater<>> right; unordered_map<int , int > mp; double get (int k) { if (k % 2 ) return left.top (); else return ((long long )left.top () + right.top ()) / 2.0 ; } vector<double > medianSlidingWindow (vector<int >& nums, int k) { for (int i = 0 ; i < k; i++) { int x = nums[i]; if (left.size () == right.size ()) { right.push (x); left.push (right.top ()); right.pop (); } else { left.push (x); right.push (left.top ()); left.pop (); } } vector<double > ans{get (k)}; for (int i = k; i < nums.size (); i++) { int balance = 0 ; int l_num = nums[i - k]; mp[l_num]++; if (!left.empty () && l_num <= left.top ()) { balance++; } else { balance--; } if (!left.empty () && nums[i] <= left.top ()) { left.push (nums[i]); balance--; } else { right.push (nums[i]); balance++; } if (balance > 0 ) { left.push (right.top ()); right.pop (); } else if (balance < 0 ) { right.push (left.top ()); left.pop (); } while (!left.empty () && mp[left.top ()] > 0 ) { mp[left.top ()]--; left.pop (); } while (!right.empty () && mp[right.top ()] > 0 ) { mp[right.top ()]--; right.pop (); } ans.push_back (get (k)); } return ans; } }
2️⃣ 懒删除堆
本题等价于:295. 数据流的中位数 + 懒删除堆
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 template <typename T, typename Compare = less<T>>class LazyHeap { priority_queue<T, vector<T>, Compare> pq; unordered_map<T, int > remove_cnt; size_t sz = 0 ; void apply_remove () { while (!pq.empty () && remove_cnt[pq.top ()] > 0 ) { remove_cnt[pq.top ()]--; pq.pop (); } } public : size_t size () { return sz; } void remove (T x) { remove_cnt[x]++; sz--; } T top () { apply_remove (); return pq.top (); } T pop () { apply_remove (); sz--; T x = pq.top (); pq.pop (); return x; } void push (T x) { if (remove_cnt[x] > 0 ) { remove_cnt[x]--; } else { pq.push (x); } sz++; } T push_pop (T x) { apply_remove (); pq.push (x); x = pq.top (); pq.pop (); return x; } }; class Solution {public : vector<double > medianSlidingWindow (vector<int >& nums, int k) { int n = nums.size (); vector<double > ans (n - k + 1 ) ; LazyHeap<int > left; LazyHeap<int , greater<int >> right; for (int i = 0 ; i < n; i++) { int in = nums[i]; if (left.size () == right.size ()) { left.push (right.push_pop (in)); } else { right.push (left.push_pop (in)); } int l = i + 1 - k; if (l < 0 ) { continue ; } if (k % 2 ) { ans[l] = left.top (); } else { ans[l] = ((long long ) left.top () + right.top ()) / 2.0 ; } int out = nums[l]; if (out <= left.top ()) { left.remove (out); if (left.size () < right.size ()) { left.push (right.pop ()); } } else { right.remove (out); if (left.size () > right.size () + 1 ) { right.push (left.pop ()); } } } return ans; } };