priority_queue(优先级队列)的用法,模拟实现priority_queue(堆排序、仿函数)

1.什么是priority_queue(优先级队列)

顾名思义,优先级队列就是一个出队有顺序的队列,每次出队列的数据都是当前队列中最大或者最小的数据。
优先级队列底层存储数据的容器默认是vector,为了实现该队列的有序性,priority_queue底层采用了堆排序,将vector中的数据采用大堆或者小堆排序的方式,实现了优先级队列。(默认是大堆,即堆顶是最大的数据)

2.priority_queue用法

构造函数
priority_queue()无参构造函数
priority_queue(InputIterator first,InputIterator last)用迭代器区间构造

容量相关函数
empty()判断优先级队列是否为空
size()返回优先级队列中元素个数

插入删除
push(x)在优先级队列中插入数据x
pop()删除优先级队列中最大(最小元素),即堆顶元素
top()返回优先级队列中最大(最小元素),即堆顶元素

3.模拟实现priority_queue

3.1 priority_queue的核心——堆排序

priority_queue代码的核心部分就是堆排序,也就是如何保证每次出队的数据都是当前队列中最大(最小)的数据。下面分析如何对一组数据进行堆排序:
我们给出一个数组,该数组逻辑上可以看作是一颗完全二叉树,我们以实现一个大堆(根节点最大,堆中某个节点的值小于它父节点的值)为例:
vector a = { 1 , 5 , 3 , 0 , 6 , 3 , 4};
在这里插入图片描述

要想将上面的堆变为大堆,我们需要将每个字树都变成大堆。

  • 首先我们要找到最小的子树:
    由图不难得出最小的子树以下标为2的节点为根节点的树,即他是最后一个叶子节点的父节点,最后一个节点表示为a.size()-1;则最小子树根节点表示为(a.size()-1-1)/2。
  • 第二步我们要将这个子树变为大根堆:
    在这里插入图片描述
  • 第三步我们把每一个子树都更新成大根堆:
    在这里插入图片描述
    最后得到的就是一个大根堆。

解释了大根堆的更新过程,下面我们来代码实现:

void AdjustDown(int parent)      //以当前节点为根节点向下调整
{
	size_t child = parent * 2 + 1;
	while (child < _c.size())
	{
		if (child + 1 < _c.size() && _c[child] < _c[child + 1])
		{
			child++;
		}
		if (_c[parent] < _c[child])
		{
			swap(_c[parent], _c[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
int main()
{
	int arr[]={1, 5, 3, 0, 6, 3, 4};
	vector<int> a(arr, arr + sizeof(arr)/sizeof(int));
	for(size_t i = (a.size() - 1 - 1) / 2); i >= 0; i--)
	{
		AdjustDown(i);               //更新子树的根节点
	}
}

3.2 完整代码

#include <vector>
template<class T>
class Less                 
{
public:
	bool operator()(const T& a, const T& b)
	{
		return a < b;
	}
};
template<class T>
class Greater
{
public:
	bool operator()(const T& a, const T& b)
	{
		return a > b;
	}
};
namespace zhy{

	template<class T , class Container=vector<T> , class Compare=Less<T>>
	//第三个模板参数是用来控制堆是大堆还是小堆的
	class priority_queue
	{
	public:
		priority_queue()
		{

		}
		template<class InputIterator>
		priority_queue(InputIterator begin, InputIterator end)
			:_c(begin,end)                                         //用迭代器区间初始化容器
		{
			//建堆
			for (size_t i = (_c.size() - 1 - 1) / 2; i >= 0; i--)
			{
				AdjustDown(i);
			}
		}
		bool empty() const
		{
			return _c.empty();
		}
		size_t size() const
		{
			return _c.size();
		}
		T& top()
		{
			return _c.front();
		}
		const T& top() const
		{
			return _c.front();
		}
		void AdjustUp(int child)                   //从当前节点向上调整
		{
			Compare com;
			int parent = (child - 1) / 2;
			while (parent >= 0)
			{
				if (com(_c[parent] , _c[child]))   //com.operator()(_c[parent],_c[child]),默认为return _c[parent]<_c[child]
				{
					swap(_c[parent], _c[child]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}
		void push(const T& value)
		{
			_c.push_back(value);         //尾插进数组
			AdjustUp(_c.size() - 1);     //调整为大堆
		}
		void AdjustDown(int parent)      //从当前节点向下调整
		{
			Compare com;                 //定义一个函数对象,即仿函数
			size_t child = parent * 2 + 1;
			while (child < _c.size())
			{
				if (child + 1 < _c.size() && com(_c[child] , _c[child + 1]))
				{
					child++;
				}
				if (com(_c[parent] , _c[child]))
				{
					swap(_c[parent], _c[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}
		void pop()
		{
			swap(_c[0], _c[_c.size() - 1]);
			_c.pop_back();
			AdjustDown(0);
		}
	private:
		Container _c;
	};
}

4 仿函数

4.1 什么是仿函数?

仿函数是一个重载了 operator() 运算符的一个类,这个类实例化出的对象具有函数的功能。
下例定义了一个仿函数:

template<class T>
class Compare
{
public:
	void operator()(const T& a, const T& b)
	{
		if (a > b)
			cout << a << ">" << b << endl;
		else if (a < b)
			cout << a << "<" << b << endl;
		else
			cout << a << "=" << b << endl;
	}
};

该仿函数的功能就是当对象调用()重载函数后,比较传入两个值的大小,并打印出来。

4.2 为什么要用仿函数?

实现了一些功能的代码经常在不同的成员函数中用到,但是不好将这些代码实现成一个成员函数。写一个公共的函数可以实现功能但是会使用到全局变量,增加维护难度。仿函数很好地解决了这个问题,它写了一个简单的类,将需要复用的代码写在了operator()重载函数中,外部函数需要使用时只要用这个类实例化出一个对象,就可以像使用函数一样来使用这个对象,完成对应功能。

4.3 仿函数的使用

上面模拟实现priority_queue的代码中就使用了仿函数,是将仿函数与模板结合,通过传入不同的模板参数来控制堆的排序方式。下面在举一个简单使用仿函数的例子:

template<class T>
class Compare
{
public:	
	void operator()(const T& a, const T& b)
	{
		if (a > b)
			cout << a << ">" << b << endl;
		else if (a < b)
			cout << a << "<" << b << endl;
		else
			cout << a << "=" << b << endl;
	}
};
int main()
{
	Compare<int> com;
	com(2, 3);
	com(1, 1);
	com(9, 1);
}

运行结果如下:
在这里插入图片描述
这里就把类实例化出的对象当作函数一样来使用了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值