c++优先队列priority_queue,及其应用

头文件#include<queue>

其实就是堆,可以这么说。对于有的问题需要用堆去实现的,就可以用优先队列。

它允许用户为队列中元素设置优先级,放置元素的时候不是直接放到队尾,而是放置到比它优先级低的元素前面,标准库默认使用 < 操作符来确定优先级关系。
priority_queue与queue一样,也是从队尾添加元素,从队头删除元素。因元素放置顺序是按元素优先级来的,所以出队出的是最高优先级的元素。优先队列具有 最高优先级先出的行为特性
它的原型是:

template <class T, class Container = vector<T>, class Compare = less<typename Container::value_type> > class priority_queue;

第一个为:元素类型
第二个为:承载优先队列的容器类型。默认是vector<>
第三个为:比较函数。带默认是less<>

注:defaultpriority_queue<T, vector<T>, less<T>>情况下:优先队列的优先级关系为值大的优先级高、值小的优先级低,而优先级高的放在队列前面,所以对于默认类型,它的内部元素总是从大到小的。

定义

1、使用系统类型(如int)

如定义:priority_queue<int> pq; 等价于priority_queue<int, vector<int>, less<int>> pq;依次push1-5-8-2-9,一直top取队头元素并pop弹出,直到队空。输出顺序会是:9-8-5-2-1
默认从小到大。值大的数优先级高,放在前面。

如果要改变优先级呢?来按从小到大排呢
这样定义:priority_queue<int, vector<int>, greater<int>> pq;//3个int要一致

2、自定义类型

使用自定义类型,就要重载<运算符

#include<queue>
#include<iostream>
using namespace std;

struct BTNode{
	int priority;
    int value;
    bool operator < (const node &a, const node &b) {  
        return a.priority < b.priority;
   
}

常用操作

  • 添加:pq.push(x)
  • 删除/弹出:pq.pop()
  • 取队头元素:x=pq.top() 注意:这里与一般的queue不一样,不用front()!!!
  • 判空:pq.empty()
  • 取大小:pq.size()

应用

关于可以用堆去实现的都可以用优先队列priority_queue。

堆排序

由于每次出队都是在剩下元素里面最大(小)的,所以只要把数组的元素放到一个priority_queue里,然后依次top+pop,得到的序列就是排序好的。
n个元素做排序,堆大小也为n。不管是插入还是删除操作,每次调整的复杂度为log(n)(堆的高度),所以算法的时间复杂度就是O(nlogn)。有人说,实际使用时候效率比快排归并排序略差,待研究。

top K

找一波数字里面最大的k个数字。
用一个k大小的优先队列。小顶堆,堆顶为整个队列中的最小值
遍历整个数据:
if pq.size()<k: 添加到pq里
if pq.size()==k:
比较各num和pq.top()的大小,如果大於则替换堆顶,即弹出队头(堆顶),然后插入到堆里面。

数据流中的中位数

之前我在 https://blog.csdn.net/u013317445/article/details/89680330 c++重拾 STL之heap(堆)这篇博客中用heap实现过。今天我用priority_queue优先队列实现一次。

**问题:**如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。

    思路:
    将数据分为两个部分,左半部分、右半部分。
    左半部分用大顶堆,保证左半部分数据的最大值在大顶堆的顶部。
    右半部分用小顶堆,保证右半部分数据的最小值在小顶堆的顶部。
    当数据总数为偶数时候,将来的num加入到右半部分,
         总数为奇数时候,将来的num加入到左半部分。
    
    整个数据流的中位数就看堆顶。
    更具体点,当数据总数为偶数时候,为两部分堆顶的平均值,
                     为奇数时候,就是左半部分的堆顶。

	调整:
    1、当总数偶数时,本计划来的数加入到右半部分(小顶堆)。
    但是如果来的数字比大顶堆的最大值还小,所以理应加入到左半部分(大顶堆),
    但是左半部分又多了一个数,所以左半部分删除最大值,并将其添加到右半部分(小顶堆),来保证左右部分数字个数平分。
    否则的话,来的数就直接加入到右半部分。
    2、当总数为奇数时,本计划...但... 同理。
class Solution {
private: 
    priority_queue<int, vector<int>, less<int>> max;//大顶堆(左半部分,堆顶要为左的最大值)
    priority_queue<int, vector<int>, greater<int>> min;//小顶堆(右半部分,堆顶要为右的最小值)
    
public:
    void Insert(int num)
    {
        
        if( ((max.size()+min.size())&1) ==0){//总数为偶数
           int maxNum= max.top();//即大顶堆的最大值(左半部分的最大值)
           if(max.size()>0 && num < maxNum){//犯错:必须写上max.size()>0 后面要max.pop()呀
               max.push(num);
               
               min.push(max.top());
               max.pop();
           }else{
               min.push(num);
           }
        }else{//总数为奇数,本计划加入到左半部分... .. 和上面同理 
            int minNum= min.top();//即小顶堆的最小值(右半部分的最小值)
            if(min.size()>0 && num > minNum){  //犯错:必须写上min.size()>0
                min.push(num);
                
                max.push(min.top());
                min.pop();
            }else{
                max.push(num);
            }
        }
    }

    double GetMedian()
    { 
        //下面这个一直报错,段错误 下面的code不就多了一个定义double吗?问题到底在哪里 还少考虑了什么情况呢?
       // if((max.size()+min.size())==0)    return 0;
       // return ((max.size()+ min.size())&1 )==0 ? (max.top()+min.top())/2.0 : max.top();//必须是2.0 不是2

        double median=0;
        if((max.size()+min.size())==0)
            return 0;
        
        if(((max.size()+min.size()) &1)==0)
        {
            median= (max.top()+min.top())/2.0;//2.0 或者给分子的每一个加强转(double)
        }else
        {
            median= min.top();
        }
        return median;
    }

};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章