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
用less ()和greater () 需要添加头文件 # include
这里只是描述用法,粘贴代码如下:

# include  
# include  
# include  
# include  
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 就是容器类型(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 
#include 
#include  
#include 
using namespace std;
int main()
{
	//对于基础类型 默认是大顶堆 
	priority_queue<int> a;
	//等同于 
	//priority_queue, less> 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  
#include  
#include  
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 
#include  
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()用法

你可能感兴趣的:(C++,数据结构与算法)