【C++】STL优先级队列(priority_queue)

【C++】STL优先级队列(priority_queue)_第1张图片

priority_queue 基本介绍

priority_queue就是优先级队列。其头文件就是queue,但是队列和优先级队列关系不大,两个是不同的数据结构。但二者都是适配器,容器适配器。

【C++】STL优先级队列(priority_queue)_第2张图片

优先级队列中存放的数据是有优先级的。

其内部有以下成员方法,其中常用的就empty, size, top, push, pop。

【C++】STL优先级队列(priority_queue)_第3张图片

直接演示一下常用方法的使用:

【C++】STL优先级队列(priority_queue)_第4张图片

我们看到用法几乎是与栈和队列一样。但是这里打印结果是排好序了(降序)。所以,优先级队列默认情况下是大的优先。

优先级队列的适配器

在这里插入图片描述

看其第二个模板参数:class Container = vector,这就是容器适配器。因此我们可以将其底层的容器改为其他的容器(list不行):

【C++】STL优先级队列(priority_queue)_第5张图片

第三个模板参数compare

在这里插入图片描述

跟sort用法很像,sort第三个参数传的是一个对象,比如说给sort传greater()就是降序,而这里传的是类型,比如说传greater就是小堆。可以看到,模板参数缺省值为less,可能有的同学不知道value_type是啥,其实就是我们日常放在容器中的元素类型,就是那个T,T可以为int、char什么的都行。所以我们默认情况下参数 Compare 就是less的类型,那么就是大堆。

【C++】STL优先级队列(priority_queue)_第6张图片

这样就变成了小堆,每次取堆顶的值就是最小值。

  • 传greater时是小堆。
  • 传less时是大堆。

使用优先级队列来解决这个问题:数组中第K个最大的元素

【C++】STL优先级队列(priority_queue)_第7张图片

题目中有要求,必须设计时间复杂度为O(N)的算法。那么先进行排序操作的同学就另寻他路吧。这道题就可以用到优先级队列,就是堆。先把堆建好,然后pop k-1次后的堆顶就是第k大的元素。

class Solution 
{
public:
    int findKthLargest(vector<int>& nums, int k) 
    {
        priority_queue<int> pq(nums.begin(), nums.end());
        while(--k)
        {
             pq.pop();
        }
        return pq.top();
    }
};

priority_queue 的模拟实现

堆的实现的核心无非就是向上调整和向下调整。而堆虽然逻辑结构上是二叉树,但是实际物理结构就是数组。我们用C++写,默认容器就是vector,因为随机访问数据的次数比较多。我们很多地方就可以直接复用vector中的函数接口,所以就需要自己动手写两个,一个是向上调整,一个是向下调整。

#include
#include
#include

namespace Flash
{
	template<class T, class Container = std::vector<T>,class Compare = less<T>>
	class priority_queue
	{
	private:
		//向上调整
		void adjust_up(size_t child)
		{
			Compare com;
			size_t parent = (child - 1) / 2;
			while (child > 0)
			{
				//if (_con[child] > _con[parent])
				//if (_con[parent] < _con[child])
				if (com(_con[parent], _con[child]))
				{
					std::swap(_con[child], _con[parent]);
				}
				else
				{
					break;
				}
				child = parent;
				parent = (child - 1) / 2;
			}
		}
		//向下调整
		void adjust_down(size_t parent)
		{
			Compare com;
			size_t child = parent * 2 + 1;
			while (child < _con.size())
			{
				if (child + 1 < _con.size() && com(_con[child], _con[child + 1]))
				{
					++child;
				}
				//if (_con[parent] < _con[child])
				if (com(_con[parent], _con[child]))
				{
					std::swap(_con[child], _con[parent]);
				}

				else
				{
					break;
				}
				parent = child;
				child = parent * 2 + 1;
			}
		}
	public:
		void push(const T& x)
		{
			_con.push_back(x);
			adjust_up(_con.size() - 1);
		}

		void pop()
		{
			std::swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			adjust_down(0);
		}

		const T& top()
		{
			return _con[0];
		}

		bool empty()const
		{
			return _con.empty();
		}

		int size()const
		{
			return _con.size();
		}
	private:
		Container _con;
	};

	template<class T>
	struct less
	{
		bool operator()(const T& L, const T& R)
		{
			return L < R;
		}
	};

	template<class T>
	struct greater
	{
		bool operator()(const T& L, const T& R)
		{
			return L > R;
		}
	};

}

上面基本功能的是实现了,但是问题是不能控制大小堆,那我们可以像库中那样,再搞一个模板参数,传一个仿函数来实现大小堆的控制。

仿函数

仿函数就是一个类,里面重载了()运算符。

【C++】STL优先级队列(priority_queue)_第8张图片【C++】STL优先级队列(priority_queue)_第9张图片

第一个ls(1, 2)乍一看就像是函数调用,但实际上就是类匿名对象调用了operator()。

【C++】STL优先级队列(priority_queue)_第10张图片

再来个greater:

【C++】STL优先级队列(priority_queue)_第11张图片
【C++】STL优先级队列(priority_queue)_第12张图片

这就是仿函数,用类来重载()来实现。调用的时候就像函数一样,在C语言阶段学过的qsort,传比较的那个参数的时候要传函数指针,但是函数指针太麻烦了,所以C++为了不再用函数指针,就搞了仿函数。

那么此时我们就可以搞第三个模板参数了。

在这里插入图片描述

传的是类型。然后把我们向上调整和向下调整中的代码改一改:

【C++】STL优先级队列(priority_queue)_第13张图片
【C++】STL优先级队列(priority_queue)_第14张图片

上面的priority_queue、less、greater都是在一个命名空间FangZhang中的,所以除了vector是复用的,剩下的都是手写的,less和greater就是刚写出来的那两个,可以直接用。

【C++】STL优先级队列(priority_queue)_第15张图片

再来强调一点:

假如说一个对象vector v,我们用sort时,传参是sort(v.begin(), v.end(), less()),而这堆这是定义对象传模板参数priority_queue,前者是传匿名对象,后者是传类型。是不一样的。不要搞混。

栈和队列还有优先级队列都是容器适配器,就是可以改变其底层所使用的容器,从而能够用不同的容器来实现其底层的函数接口。

你可能感兴趣的:(C++,c++)