C++中兩種實現堆的方式:make_heap和priority_queue
在實現一些演算法的時候,會用到大大頂堆和小頂堆,下面介紹兩種在C++中實現隊的兩種方式。
1 make_heap()方式
make_heap(): 生成堆,他有兩個引數,也可以有三個引數,前兩個引數是指向迭代器的開始元素和指向迭代器的結束元素。第三個引數是可選的,可以用偽函式less()和greater()來生成大頂堆和小頂堆,其中type為元素型別。**如果只傳入前兩個引數,預設是生成大頂堆,**即第三個引數的預設值的less()。
**push_heap():**是在堆的基礎上進行資料的插入操作,引數與make_heap()相同,需要注意的是,只有make_heap()和push_heap()同為大頂堆或小頂堆,才能插入。
**pop_heap():是在堆的基礎上,彈出堆頂元素。這裡需要注意的是,pop_heap()並沒有刪除元素,而是將堆頂元素和陣列最後一個元素進行了替換,如果要刪除這個元素,還需要對陣列進行pop_back()操作。可見
若用make_heap(), pop_heap(), push_heap(),需要新增標頭檔案
# include <algorithm>
。用less ()和greater () 需要新增標頭檔案
# include <functional>
。這裡只是描述用法,貼上程式碼如下:
# include <iostream>
# include <functional>
# include <vector>
# include <algorithm>
using namespace std;
void printVec(vector<int> nums)
{
for (int i = 0; i < nums.size(); ++i)
cout << nums[i] << " ";
cout << endl;
}
int main(void)
{
int nums_temp[] = { 8, 3, 4, 8, 9, 2, 3, 4 } ;
vector<int> nums(nums_temp, nums_temp + 8);
cout << "sorce nums: "; printVec(nums);
cout << "(預設)make_heap: ";
make_heap(nums.begin(), nums.end()); //預設是大頂堆,只是將最大值的元素和第一個元素互換,其他元素的順序不變
printVec(nums);
cout << "(less)make_heap: ";
make_heap(nums.begin(), nums.end(), less<int>()); //建立大頂堆,只是將最大值的元素和第一個元素互換,其他元素的順序不變
printVec(nums);
cout << "(greater)make_heap: ";
make_heap(nums.begin(), nums.end(), greater<int>()); //建立小頂堆,只是將最小值的元素和第一個元素互換,其他元素的順序不變
printVec(nums);
cout << "此時,nums為小頂堆" << endl;
cout << "push_back(3)" << endl;
nums.push_back(3); //在vector的末尾新增元素
cout << "忽略第三個引數,即為預設)push_heap: ";
//下面的一行程式碼,相當於是往vector中新增元素後更新heap,執行到此處會出現錯誤,只有make_heap()和push_heap()同為大頂堆或小頂堆,才能更新。
//但是此處push_heap使用預設引數,是更新大頂堆,但是上面最後一個make_heap建立的是小頂堆,衝突。
//push_heap(nums.begin(), nums.end());
printVec(nums);
cout << "第三個引數為greater: ";
push_heap(nums.begin(), nums.end(), greater<int>()); //指定引數後不發生衝突
printVec(nums);
cout << "(做替換)預設引數pop_heap: ";
//pop_heap()並沒有刪除元素,而是將堆頂元素和陣列最後一個元素進行了替換。和上面的push_heap一樣第三個引數不指定的話,會發生衝突
//pop_heap(nums.begin(), nums.end());
printVec(nums);
cout << "(做替換)第三個引數為greater,pop_heap: ";
pop_heap(nums.begin(), nums.end(), greater<int>()); //指定引數後不發生衝突
printVec(nums);
cout << "pop_back(): ";
nums.pop_back(); //刪除vector末尾的元素
printVec(nums);
}
使用的時候可以不make_heap,而直接push_heap,示例如下面的這道劍指offer上的題目資料流中的中位數。
題目描述:
如何得到一個數據流中的中位數?如果從資料流中讀出奇數個數值,那麼中位數就是所有數值排序之後位於中間的數值。如果從資料流中讀出偶數個數值,那麼中位數就是所有數值排序之後中間兩個數的平均值。我們使用Insert()方法讀取資料流,使用GetMedian()方法獲取當前讀取資料的中位數。
答案:
class Solution {
public:
void Insert(int num)
{
if (((min.size() + max.size()) & 1) == 0){ //偶數,如果你不知道運算子的優先順序,請一定帶括號
if (max.size() > 0 && num < max[0]){
max.push_back(num);
push_heap(max.begin(), max.end(), less<int>());
num = max[0];
pop_heap(max.begin(), max.end(), less<int>());
max.pop_back();
}
min.push_back(num);
push_heap(min.begin(), min.end(), greater<int>());
}
else{
if (min.size() > 0 && num > min[0]){
min.push_back(num);
push_heap(min.begin(), min.end(), greater<int>());
num = min[0];
pop_heap(min.begin(), min.end(), greater<int>());
min.pop_back();
}
max.push_back(num);
push_heap(max.begin(), max.end(), less<int>()); //push_back之後一定要使用push_head更新heap
}
}
double GetMedian()
{
int size = min.size() + max.size();
if (size<1)
throw "invalid!";
double median = 0;
if ((size & 1) == 1){
median = min[0];
}
else{
median = (double)((min[0] + max[0]) / 2.0);
}
return median;
}
private:
vector<int> min; //偶數
vector<int> max; //奇數
};
2 priority_queue方式
使用C++中優先佇列也可以實現堆,使用方法如下。
首先,優先佇列具有佇列的所有特性,包括基本操作,只是在這基礎上添加了內部的一個排序,它本質是一個堆實現的。
定義: priority_queue<Type, Container, Functional>
Type 就是資料型別,Container 就是容器型別(Container必須是用陣列實現的容器,比如vector,deque等等,但不能用 list。STL裡面預設用的是vector),Functional 就是比較的方式,當需要用自定義的資料型別時才需要傳入這三個引數,使用基本資料型別時,只需要傳入資料型別,預設是大頂堆 。
一般的使用方式:
/升序佇列
priority_queue <int,vector<int>,greater<int> > q;
//降序佇列
priority_queue <int,vector<int>,less<int> >q;
/*greater和less是std實現的兩個仿函式(就是使一個類的使用看上去像一個函式。
其實現就是類中實現一個operator(),這個類就有了類似函式的行為,就是一個仿函式類了)*/
1. 使用基本型別的例子
#include<iostream>
#include <functional>
#include <queue>
#include <string>
using namespace std;
int main()
{
//對於基礎型別 預設是大頂堆
priority_queue<int> a;
//等同於
//priority_queue<int, vector<int>, less<int>> a; //這裡一定要有空格,不然成了右移運算
priority_queue<int, vector<int>, greater<int> > c; //這樣就是小頂堆
priority_queue<string> b;
for (int i = 0; i < 5; i++)
{
a.push(i);
c.push(i);
}
while (!a.empty())
{
cout << a.top() << ' ';
a.pop();
}
cout << endl;
while (!c.empty()) {
cout << c.top() << ' ';
c.pop();
}
cout << endl;
b.push("abc");
b.push("abcd");
b.push("cbd");
while (!b.empty())
{
cout << b.top() << ' ';
b.pop();
}
cout << endl;
return 0;
}
輸出
4 3 2 1 0
0 1 2 3 4
cbd abcd abc
2.pari的比較
先比較第一個元素,第一個相等比較第二個。順便學習pair的使用方式。
#include <iostream>
#include <queue>
#include <vector>
using namespace std;
int main()
{
priority_queue<pair<int, int> > a;
pair<int, int> b(1, 2);
pair<int, int> c(1, 3);
pair<int, int> d(2, 5);
a.push(d);
a.push(c);
a.push(b);
while (!a.empty())
{
cout << a.top().first << ' ' << a.top().second << '\n';
a.pop();
}
}
3. 使用自定義的型別
#include <iostream>
#include <queue>
using namespace std;
//方法1
struct tmp1 //運算子過載<
{
int x;
tmp1(int a) { x = a; }
bool operator<(const tmp1& a) const {
return x < a.x; //大頂堆
}
};
//方法2
struct tmp2 //重寫仿函式
{
bool operator() (tmp1 a, tmp1 b)
{
return a.x < b.x; //大頂堆
}
};
int main()
{
tmp1 a(1);
tmp1 b(2);
tmp1 c(3);
priority_queue<tmp1> d;
d.push(b);
d.push(c);
d.push(a);
while (!d.empty()) {
cout << d.top().x << '\n';
d.pop();
}
cout << endl;
priority_queue<tmp1, vector<tmp1>, tmp2> f;
f.push(c);
f.push(b);
f.push(a);
while (!f.empty()) {
cout << f.top().x << '\n';
f.pop();
}
}
輸出
3
2
1
3
2
1
參考文章: