1. 程式人生 > >C++中兩種實現堆的方式:make_heap和priority_queue

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()操作。可見

想要刪除棧頂元素,需要先將棧頂元素換到容器末尾,然後在刪除容器末尾的元素。**同樣需要注意的是pop_heap引數與make_heap()相同,只有make_heap()和pop_heap()同為大頂堆或小頂堆,才能彈出。
若用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

參考文章:

  1. c++優先佇列(priority_queue)用法詳解
  2. make_heap(), pop_heap(), push_heap()用法