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;
    }

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