C++ STL - 优先级队列及其模拟实现

目录

0. 引言

1. priority_queue 介绍 

1.1 构造函数 

1.2 priority_queue 接口函数使用 

1.3 仿函数 

 1.4 题目练习

 2. priority_queue 模拟实现

2.1基本框架:

2.2 默认构造函数

2.3 基本函数

2.4 堆的向上以及向下调整


0. 引言

优先队列 (priority_queue) 是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。优先队列和堆本质是一样的,以数组形式存储的完全二叉树。

1. priority_queue 介绍 

1.1 构造函数 

我们可以看到有两种构造方式,一个是构造一个空对象,另一个是通过迭代器的区间来构造,默认的构造出的是大堆

priority_queue<int> pq1; //直接构造空对象

接下来我们分别以大堆和小堆的方式来构造对象:

	vector<int> v1 = {3,2,7,6,0,4,1,9,8,5};
	priority_queue<int, vector<int>, less<int>> pq1(v1.begin(), v1.end());
                                    //less-大堆
	while (!pq1.empty())
	{
		cout << pq1.top() << " ";
		pq1.pop();
	}
	cout << endl;

	priority_queue<int, vector<int>, greater<int>> pq2(v1.begin(), v1.end());
                                    //greater-小堆
	while (!pq2.empty())
	{
		cout << pq2.top() << " ";
		pq2.pop();
	}
	cout << endl;

	priority_queue<int> pq3(v1.begin(), v1.end());
                                    //默认大堆
	while (!pq3.empty())
	{
		cout << pq3.top() << " ";
		pq3.pop();
	}
	cout << endl;

因此我们得出: less - 大堆, greater - 小堆。 

1.2 priority_queue 接口函数使用 

接口函数主要包括以下:

函数说明
empty检测优先级队列是否为空,是返回true,否则返回 false
top返回优先级队列中最大(最小元素),即堆顶元
push在优先级队列中插入元素x
pop删除优先级队列中最大(最小)元素,即堆顶元素

1.3 仿函数 

仿函数又名函数对象 function objects 仿函数的主要作用是借助类和运算符重载,做到同一格式兼容所有函数。由于模板将 less 用作大堆,而 greater 用做小堆,是在有点别扭,如果是我们自己实现仿函数的化,肯定会按照习惯来写,less 表示小堆,greater 表示大堆。例如:

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

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

 1.4 题目练习

优先级队列适合来进行TOPK 以及 排序问题,因为其底层是和堆一模一样的。现在我们一起来看下面这道题:

这题如果不关心时间复杂度,直接利用 sort 排序将会很简单:

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) 
    {
        sort(nums.begin(),nums.end());
        return nums[nums.size()-k];    
    }
};

 当我们使用优先级队列时,时间复杂度会更好:

//大堆
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) 
    {
    priority_queue<int> pq1(nums.begin(), nums.end());
      for(int i = 0;i < k-1; i++)
      {
            pq1.pop();
      }
       return pq1.top(); 
    }
};
//小堆
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) 
    {
    priority_queue<int,vector<int>, greater<int>> pq1(nums.begin(), nums.begin()+k);
    for(int i = k ;i < nums.size(); i++)
    {
        if(nums[i] > pq1.top())
        {
            pq1.pop();
            pq1.push(nums[i]);
        }
    }
    return pq1.top(); 
    }  
};

 2. priority_queue 模拟实现

优先级队列的模拟实现,难点在于堆的向上和向下调整。我们首先展现整体代码,然后再逐一分进行分析。

2.1 整体代码

我们单独创建了一个 PriorityQueue.h 文件。

#pragma once
#include<vector>
#include<iostream>
using namespace std;
namespace LHY
{
	template<class T, class Container = vector<T>>
	class priority_queue
	{
	public:
		void adjust_up(int child)
		{
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				if (_con[parent] < _con[child])
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}
		void adjust_down(int parent)
		{
			size_t child = parent * 2 + 1;
			while (child < _con.size())
			{


				if (child + 1 < _con.size()
					&& _con[child] < _con[child + 1])
				{
					++child;
				}

				if (_con[parent] < _con[child])
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}
		void push(const T& x)
		{
			_con.push_back(x);
			adjust_up(_con.size() - 1);
		}

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

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

		size_t size()
		{
			return _con.size();
		}

		bool empty()
		{
			return _con.empty();
		}
	private:
		Container _con;
	};

从中我们可以看到私有成员变量为底层容器对象。即 Container _con; class Container = vector<T> 。

2.2 基本函数

基本函数为:

void push(const T& x)
{
	_con.push_back(x);
	adjust_up(_con.size() - 1);
}

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

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

size_t size()
{
	return _con.size();
}

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

我们可以清楚的看到,优先级队列的插入和删除,返回大小以及判空直接调用底层容器接口即可。而因为需要满足堆的性质,push 和 pop 则需要向上和向下调整堆。此处我们默认建立的是大堆,当push一个数比其父节点大,则需要交换位置,即向上调整。当 pop 操作则是先首尾交换,再删除尾元素,此时不满足堆条件,顶部元素则需要向下调整。如下图所示:

2.3 模拟实现 

void test_priority_queue()
{
	priority_queue<int> pq;
	pq.push(1);
	pq.push(0);
	pq.push(5);
	pq.push(2);
	pq.push(1);
	pq.push(7);

	while (!pq.empty())
	{
		cout << pq.top() << " ";
		pq.pop();
	}
	cout << endl;
}

2.4 仿函数切换大小堆 

我们在 1.3 介绍了仿函数及其实现,那么我们能不能应用到上面的代码中呢?答案是当然可以。

代码如下:

namespace LHY
{
	template<class T>
	struct less
	{
		bool operator()(const T& x, const T& y)
		{
			return x < y;
		}
	};

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

	template<class T, class Container = vector<T>,class Comapre = less<T>>
	class priority_queue
	{
	public:
		void adjust_up(int child)
		{
			Comapre com;
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				//if (_con[parent] < _con[child])
				if (com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}
		void adjust_down(int parent)
		{
			size_t child = parent * 2 + 1;
			Comapre com;
			while (child < _con.size())
			{


				/*if (child + 1 < _con.size()
					&& _con[child] < _con[child + 1])*/
				if (child + 1 < _con.size()
					&& com(_con[child], _con[child + 1]))
				{
					++child;
				}

				/*if (_con[parent] < _con[child])*/
				if (com(_con[parent], _con[child]))
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}
		void push(const T& x)
		{
			_con.push_back(x);
			adjust_up(_con.size() - 1);
		}

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

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

		size_t size()
		{
			return _con.size();
		}

		bool empty()
		{
			return _con.empty();
		}
	private:
		Container _con;
	};

    template<class T, class Container = vector<T>,class Comapre = less<T>>

   if (com(_con[parent], _con[child]))等等的变化使得我们可以改变大小堆。例如:

2.5 日期类 

假设我们现在的数据不是 vector<int> 而是 日期类对象,会发生什么情况呢?我们先看日期类代码:

class Date
	{
	public:
		Date(int year = 1900, int month = 1, int day = 1)
			: _year(year)
			, _month(month)
			, _day(day)
		{}

		bool operator<(const Date& d)const
		{
			return (_year < d._year) ||
				(_year == d._year && _month < d._month) ||
				(_year == d._year && _month == d._month && _day < d._day);
		}

		bool operator>(const Date& d)const
		{
			return (_year > d._year) ||
				(_year == d._year && _month > d._month) ||
				(_year == d._year && _month == d._month && _day > d._day);
		}

		friend ostream& operator<<(ostream& _cout, const Date& d)
		{
			_cout << d._year << "-" << d._month << "-" << d._day;
			return _cout;
		}

	private:
		int _year;
		int _month;
		int _day;
	};

	class PDateLess
	{
	public:
		bool operator()(const Date* p1, const Date* p2)
		{
			return *p1 < *p2;
		}
	};

	class PDateGreater
	{
	public:
		bool operator()(const Date* p1, const Date* p2)
		{
			return *p1 > *p2;
		}
	};

这时候我们创建数据为Date 的优先级队列(大堆),取堆顶元素(判断是否能对自定义类型进行正确调整)

此时没有问题, 因为在实际比较时,调用的是 Date 自己的比较逻辑。

那么如果是Date* 呢?我们再来看:

我们发现多次运行结果不一样 ,因为此时调用的是指针的比较逻辑(地址是随机的,因此结果也是随机的)。

那么,我们要如何去解放呢?可以专门创建一个为Date* 比较的仿函数。

class PDateLess
	{
	public:
		bool operator()(const Date* p1, const Date* p2)
		{
			return *p1 < *p2;
		}
	};

	class PDateGreater
	{
	public:
		bool operator()(const Date* p1, const Date* p2)
		{
			return *p1 > *p2;
		}
	};

这样便没有问题了:

  • 27
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值