【C++】priority_queue使用 & C++实现priority_queue

前言

我们前面学习了queue队列和deque双端队列,今天要学习的priority_queue是优先级队列,和queue在同一个头文件中

在这里插入图片描述
事不宜迟,那我们就开始今天的学习


一. 结构

普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。
在优先级队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先级队列具有最高级先出的行为特征。通常采用堆数据结构来实现。

1. 堆

在数据结构中,我们学到一个特殊的二叉树——堆
其实堆的本质是一个数组,只是通过下标之间的关系,可以被转换成完全二叉树
在这里插入图片描述


2. priority_queue

而priority_queue就是一种堆,不过堆并不是完全有序,就像上图的大顶堆。
而priority_queue是完全有序的堆
在这里插入图片描述


3. 实例化

在这里插入图片描述

priority_queue的类模板分别有三个参数

参数名称参数说明
T元素类型
Container底层容器(默认是vector)
Compare比较方式(默认是less)

前两个参数我们在stack和queue中已经见过,这里就不再赘述了。
主要是第三个参数Compare
在稍后的模拟实现中,我们会再次谈及这个模板参数的强大


二. 函数接口

priority_queue的函数接口如下所示
在这里插入图片描述

函数名称函数功能
(constructor)构造函数
empty判空
size获取优先级队列的元素个数
top取堆顶元素
push插入
emplace插入(和push有所不同)
pop删除堆顶数据
swap交换两个堆的元素

函数接口和stack,queue基本一致,这里就不再过多赘述


三. 模拟实现

那么接下来,我们就来模拟实现priority_queue吧
我们先模拟实现一个优先级高的队列,也就是大堆

1. 基础框架

第一次的实现,我们类模板里不写class Compare

template<class T,class Container=vector<T>>
	class priority_queue
	{
	private:
		Container _con;
	};

基本框架和stack和queue一样

2. 函数接口实现

因为我们套用了底层容器,所以很多函数接口都可以复用叠层容器的函数接口来实现

empty判空

//判空
bool empty()
{
	return _con.empty();
}

获取优先级队列的元素个数

//返回优先级队列的元素个数
size_t size()
{
	return _con.size();
}

取堆顶元素

//取堆顶元素
const T& top()
{
	return _con[0];
}

插入
这里涉及到堆的算法,不懂的读者可以参看【算法基础】八大排序中的堆排
或者看其他大佬的文章,这里仅以注释作简单说明

//向上调整法
void adjust_up(size_t child)
{
	size_t parent = (child - 1) / 2;

	while (child > 0)
	{
		//建大堆——降序
		//双亲节点比孩子节点小才交换
		if (_con[parent] < _con[child])
		{
			swap(_con[parent] , _con[child]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			//因为插入前堆本身就是有序的
			//所以一旦遇到双亲比孩子大的,就代表再上面的节点都比该孩子节点大
			//所以排序完毕,直接跳出循环
			break;
		}
	}
}

//插入
void push(const T&val)
{
	//尾插元素
	_con.push_back(val);
	//向上调整法
	adjust_up(_con.size()-1);
}

删除

		//向下调整法
		void adjust_down(size_t 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[parent],_con[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

		//删除堆顶元素
		void pop()
		{
			assert(!empty());

			//将堆顶的元素和最后的元素交换
			swap(_con[0], _con[_con.size() - 1]);
			//删除最后一个元素
			_con.pop_back();
			//向下调整法重建堆
			adjust_down(0);
		}

3.简单测试

void test1()
	{
		priority_queue<int>pq;
		pq.push(1);
		pq.push(3);
		pq.push(4);
		pq.push(5);
		pq.push(3);
		pq.push(7);
		pq.push(10);
		pq.push(15);
		pq.push(2);

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

在这里插入图片描述

4. 完整代码

#pragma once
#include<vector>
#include<assert.h>
#include<algorithm>
#include<iostream>

using namespace std;

template<class T,class Container=vector<T>>
	class priority_queue
	{
	public:
		//判空
		bool empty()
		{
			return _con.empty();
		}

		//返回优先级队列的元素个数
		size_t size()
		{
			return _con.size();
		}

		//取堆顶元素
		const T& top()
		{
			return _con[0];
		}

		//向上调整法
		void adjust_up(size_t child)
		{
			size_t parent = (child - 1) / 2;

			while (child > 0)
			{
				//建大堆——降序
				//双亲节点比孩子节点小才交换
				if (_con[parent] < _con[child])
				{
					swap(_con[parent] , _con[child]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					//因为插入前堆本身就是有序的
					//所以一旦遇到双亲比孩子大的,就代表再上面的节点都比该孩子节点大
					//所以排序完毕,直接跳出循环
					break;
				}
			}
		}

		//插入
		void push(const T&val)
		{
			//尾插元素
			_con.push_back(val);
			//向上调整法
			adjust_up(_con.size()-1);
		}

		//向下调整法
		void adjust_down(size_t 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[parent],_con[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

		//删除堆顶元素
		void pop()
		{
			assert(!empty());

			//将堆顶的元素和最后的元素交换
			swap(_con[0], _con[_con.size() - 1]);
			//删除最后一个元素
			_con.pop_back();
			//向下调整法重建堆
			adjust_down(0);
		}

	private:
		Container _con;
	};

	void test1()
	{
		priority_queue<int>pq;
		pq.push(1);
		pq.push(3);
		pq.push(4);
		pq.push(5);
		pq.push(3);
		pq.push(7);
		pq.push(10);
		pq.push(15);
		pq.push(2);

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

四. Compare—仿函数

1. 介绍

首先我们介绍一下仿函数

在之前的学习,我们学习了很多的运算符重载,也说了哪几个运算符没有重载。
有5个运算符不能重载,“ .* ” , “ ::” , “sizeof” , “ ?:” , “ . ”
那在我们平常使用的,有一个运算符我们还没有重载过 — ()

以下我们给个简单的样例

struct Less
{
	bool operator()(int x, int y)
	{
		return x < y;
	}
};

int main()
{
	int x = 10;
	int y = 10;
	Less less;
	cout << less(x, y) << endl;

	return 0;
}

诶,仅看代码,是不是感觉这个Less类,被拿来当作函数一样使用了呢?
这就是仿函数——重载()

2. 思考

再返回来,我们不是将priority_queue的实现,怎么突然将仿函数了呢?
这里我们要先抛出一个问题:

上面的priority_queue的实现,我们是不是设置成了优先级高的队列,或者叫大堆
那小堆怎么写了?
有人可能就在想,简单,改一下向上调整法和向下调整法的比较部分的代码就好了
那难道我们每次切换大小优先级的时候,都需要改代码吗?
答案肯定是不

那如何控制这个比较部分呢?
这就要应用到我们刚学到的仿函数了。


3. 仿函数应用

参看文献,比较方式有两个类——less和greater
二者的头文件都是functional

在这里插入图片描述
在这里插入图片描述


我们模仿标准库的写法
less是建大堆,greater是建小堆

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

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

在类模板的参数列表中添加一个参数,并默认为less
这样在比较部分我们就可以套用仿函数,实现内部的自动推导,这就是封装的魅力
在这里插入图片描述

在这里插入图片描述

注意,这里仿函数传参的位置不可以交换,要仔细思考位置
其他部分代码均未改变

4. 测试

void test1()
	{
		priority_queue<int>pq;
		pq.push(1);
		pq.push(3);
		pq.push(4);
		pq.push(5);
		pq.push(3);
		pq.push(7);
		pq.push(10);
		pq.push(15);
		pq.push(2);

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

		priority_queue<int, vector<int>, greater<int>>pq1;
		pq1.push(1);
		pq1.push(3);
		pq1.push(4);
		pq1.push(5);
		pq1.push(3);
		pq1.push(7);
		pq1.push(10);
		pq1.push(15);
		pq1.push(2);

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

在这里插入图片描述

5. 完整代码

#pragma once
#include<vector>
#include<assert.h>
#include<algorithm>
#include<iostream>

using namespace std;

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

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

	template<class T,class Container=vector<T>,class Compare=less<T>>
	class priority_queue
	{
	public:
		//判空
		bool empty()
		{
			return _con.empty();
		}

		//返回优先级队列的元素个数
		size_t size()
		{
			return _con.size();
		}

		//取堆顶元素
		const T& top()
		{
			return _con[0];
		}

		//向上调整法
		void adjust_up(size_t child)
		{
			Compare Com;

			size_t parent = (child - 1) / 2;

			while (child > 0)
			{
				if (Com(_con[parent],_con[child]))
				{
					swap(_con[parent] , _con[child]);
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}

		//插入
		void push(const T&val)
		{
			//尾插元素
			_con.push_back(val);
			//向上调整法
			adjust_up(_con.size()-1);
		}

		//向下调整法
		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 (Com(_con[parent],_con[child]))
				{
					swap(_con[parent],_con[child]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

		//删除堆顶元素
		void pop()
		{
			assert(!empty());

			//将堆顶的元素和最后的元素交换
			swap(_con[0], _con[_con.size() - 1]);
			//删除最后一个元素
			_con.pop_back();
			//向下调整法重建堆
			adjust_down(0);
		}

	private:
		Container _con;
	};

	void test1()
	{
		priority_queue<int>pq;
		pq.push(1);
		pq.push(3);
		pq.push(4);
		pq.push(5);
		pq.push(3);
		pq.push(7);
		pq.push(10);
		pq.push(15);
		pq.push(2);

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

		priority_queue<int, vector<int>, greater<int>>pq1;
		pq1.push(1);
		pq1.push(3);
		pq1.push(4);
		pq1.push(5);
		pq1.push(3);
		pq1.push(7);
		pq1.push(10);
		pq1.push(15);
		pq1.push(2);

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

结束语

本章对priority_queue的学习就到这了,感谢阅读
如果觉得本篇文章对你有所帮助的话,不妨点个赞支持一下博主,拜托啦,这对我真的很重要
在这里插入图片描述

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值