当前位置: 首页 > article >正文

C++priority_queue模拟实现

C++priority_queue模拟实现

  • 1.priority_queue基本概念
  • 2.priority_queue基本结构
  • 3.size()成员函数
  • 4.empty()成员函数
  • 5.top()成员函数
  • 6.push()成员函数
  • 7.pop()成员函数
  • 8.构造函数
  • 9.完整代码

🌟🌟hello,各位读者大大们你们好呀🌟🌟
🚀🚀系列专栏:【C++的学习】
📝📝本篇内容:priority_queue基本概念;priority_queue基本结构;size()成员函数;empty()成员函数;top()成员函数;push()成员函数;pop()成员函数;构造函数;完整代码
⬆⬆⬆⬆上一篇:C++模拟实现queue
💖💖作者简介:轩情吖,请多多指教(> •̀֊•́ ) ̖́-

1.priority_queue基本概念

priority_queue是我们常说的优先级队列,是一个容器适配器,它底层默认使用了vector容器,并且在此基础上使用了堆算法。因为牵扯到了堆,那就跟二叉树搭上了点关系。我们的二叉树中有一种树叫做完全二叉树,什么是完全二叉树呢?
完全二叉树:前N-1层是满的,最后一层可以不满,但是必须从左往右是连续的。要注意的是满二叉树是一种特殊的完全二叉树
在这里插入图片描述
根据它的这个特性(没有任何节点漏洞),我们可以将完全二叉树存储到数组中,在C++中我们就可以把它放入vector中,这种将数组的方式来表述tree称为隐式表述法
那么任何一个节点求它的父节点以及左右子节点公式如下:
若i>0,i位置结点的双亲序号:(i-1)/2=parent;i=0,i为根节点编号,无双亲结点
设n为数组的大小,若2i+1<n,左孩子序号:2i+1=leftchild;2i+1>=n否则无左孩子
设n为数组的大小,若2i+2<n,右孩子序号:2i+2=rightchild;2i+2>=n否则无右孩子

前面说了堆算法是建立在树上的,那么什么是堆呢?
我们的堆分为两种,大根堆或者是小根堆
大堆:树中所有的父亲都大于等于孩子
小堆:树中的所有父亲都小于等于孩子

优先级队列只能在底端加入元素,从顶端取出元素,这也是为什么是队列原因,并且我们取出的值是所有元素中最大的或者最小的(优先队列中位于顶部的元素)
在这里插入图片描述

2.priority_queue基本结构

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <vector>
using namespace std;
namespace lnb
{
	//用来比较的仿函数
	template<class T>
	struct less
	{
		bool operator()(const T& x,const T& y)const
		{
			//less是<,greater是>
			return x < y;
		}
	};

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

	};

	template<class T, class Container=vector<T>, class Compare=less<T>>
	class priority_queue
	{
	public:

	private:
		Container _con;//用来存储完全二叉树
		Compare _com;//用来比较的方法
	};

}

3.size()成员函数

		//有效元素个数
		size_t size()const
		{
			return _con.size();
		}

4.empty()成员函数

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

5.top()成员函数

		//获取最大或最小的元素(位于顶部,即下标为0的元素)
		const T& top()const
		{
			return _con[0];
		}

在这里插入图片描述
所获取的值如上图

6.push()成员函数

		void push(const T& val)
		{
			//1.先进行插入
			_con.push_back(val);
			//2.进行向上调整
			AdjustUp(_con.size()-1);
		}

	private:
		void AdjustUp(int child)
		{
			//1.先找到父节点
			int parent = (child - 1) / 2;//公式前面讲过
			while (child > 0)//保证不超过根节点,最后一次循环只能是child为1或2,parent为0
			{
			//2.判断是否父节点小于(大于)孩子节点
			//if(Compare()(_con[parent],_con[child]))//使用匿名对象来判断也可以
			if (_com(_con[parent], _con[child]))//默认是大堆,是判断是否父节点小于孩子节点
				{
					//3.进行交换
					swap(_con[parent], _con[child]);
					//4.调整parent,child,继续向上调整直到不需要调换或者到根节点
					child = parent;//父节点成为孩子节点继续往上调整
					parent = (child - 1) / 2;
				}
				else
				{
					//已经不需要调整了
					break;
				}
			}
		}

在这里插入图片描述
当把元素插入后,只需要进行一个向上调整即可,我们默认是大堆哈,这是为了保证当我们大堆的一个结构,所有的父结点大于孩子节点。因此插入的一个元素需要进行调整,如果它大于自己的父结点,就往上调整和父节点进行交换,孩子节点就到了父节点的位置,然后继续向上调整直到父节点大于孩子节点或者调整到根节点结束
上面的代码中,我们使用到了仿函数来进行判断,但是我们如果没有它呢?那么对于泛型编程是一个巨大的问题,使用者无法通过外部的模板参数来控制内部的比较,只能通过直接的><来判断。

7.pop()成员函数

		void pop()
		{
			//1.删除的是最大(最小)的元素,所以先将_con[0]和_con[_con.size()-1]交换
			swap(_con[0], _con[_con.size() - 1]);
			//2.容器进行尾删
			_con.pop_back();
			//3.交换后,就可以将_con[0]向下调整
			AdjustDown(0);
		}


	private:
		//向下调整
		void AdjustDown(int parent)//默认为大堆
		{
			//1.先求出左孩子节点
			int child = parent * 2 + 1;
			while (child < _con.size())//保证孩子节点不会超过容器的大小
			{
			//2.保证大(小)根堆的特性,选出孩子节点中较大(小)的那个
				if (child+1<_con.size()&&_com(_con[child],_con[child + 1]))//确保右孩子的存在
				{
					child++;
				}
			//3.进行判断是否父节点小于(大于)孩子节点
				if (_com(_con[parent], _con[child]))
				{
			//4.进行交换
					swap(_con[parent], _con[child]);
			//5.孩子节点成为父节点,并重新找左孩子节点
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					//父节点大于(小于)孩子节点即可结束
					break;
				}

			//6.一直调整,直到父节点大于(小于)孩子节点或者父节点没有孩子节点了
			}

		}

在这里插入图片描述 上面已经给出了代码和交换的图片,我们首先来讲一下为什么需要通过交换才能删除这个最大(最小)元素,究其原因还是它在根节点的原因,没有办法对它进行一个直接删除,一旦直接删除,就会导致整体的一个堆结构就乱掉了,那么最好的方法就是和尾元素进行交换,然后进行向下调整,这样不但保证了结构的完整性,效率还高
向上调整如下图:
在这里插入图片描述

8.构造函数

	//使用迭代器构造
		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
			:_con(first,last)
		{
			//完全混乱的结构,需要进行调整
			//第一个-1是找到最后一个元素,第二个-1是为了找到父节点
			int end = ((_con.size() - 1) - 1) / 2;//找到最后一个元素的父节点
			while (end >= 0)//直到调整到根节点
			{
				AdjustDown(end);//对于子树进行从下往上挨个进行向下调整
				end--;
			}
		}
		
		//默认构造函数
		priority_queue()
		{

		}

默认构造就没什么好讲的,主要是使用迭代器构造,这个也是一个难点,给一堆混乱的数据,如何变成符合堆特性
在这里插入图片描述
想要保证一组毫无序列的元素变成堆,就需要保证每一个子树的父节点都大于(小于)孩子节点,因此我们只能从最后一棵子树开始向下调整,这样当调整到上一层时,下层都已经是堆了,只需要对当前节点也是向下调整即可。一直到根节点完成最后一次向下调整就可以完成堆的构建。

9.完整代码

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <vector>
using namespace std;
namespace lnb
{
	//用来比较的仿函数
	template<class T>
	struct less
	{
		bool operator()(const T& x,const T& y)const
		{
			//less是<,greater是>
			return x < y;
		}
	};

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

	};

	template<class T, class Container=vector<T>, class Compare=less<T>>
	class priority_queue
	{
	public:
		//使用迭代器构造
		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
			:_con(first,last)
		{
			//完全混乱的结构,需要进行调整
			//第一个-1是找到最后一个元素,第二个-1是为了找到父节点
			int end = ((_con.size() - 1) - 1) / 2;//找到最后一个元素的父节点
			while (end >= 0)//直到调整到根节点
			{
				AdjustDown(end);//对于子树进行从下往上挨个进行向下调整
				end--;
			}
		}
		
		//默认构造函数
		priority_queue()
		{

		}

		//有效元素个数
		size_t size()const
		{
			return _con.size();
		}

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

		//获取最大或最小的元素(位于顶部,即下标为0的元素)
		const T& top()const
		{
			return _con[0];
		}




		void push(const T& val)
		{
			//1.先进行插入
			_con.push_back(val);
			//2.进行向上调整
			AdjustUp(_con.size() - 1);
		}

		void pop()
		{
			//1.删除的是最大(最小)的元素,所以先将_con[0]和_con[_con.size()-1]交换
			swap(_con[0], _con[_con.size() - 1]);
			//2.容器进行尾删
			_con.pop_back();
			//3.交换后,就可以将_con[0]向下调整
			AdjustDown(0);
		}


	private:
		//向下调整
		void AdjustDown(int parent)//默认为大堆
		{
			//1.先求出左孩子节点
			int child = parent * 2 + 1;
			while (child < _con.size())//保证孩子节点不会超过容器的大小
			{
			//2.保证大(小)根堆的特性,选出孩子节点中较大(小)的那个
				if (child+1<_con.size()&&_com(_con[child],_con[child + 1]))//确保右孩子的存在
				{
					child++;
				}
			//3.进行判断是否父节点小于(大于)孩子节点
				if (_com(_con[parent], _con[child]))
				{
			//4.进行交换
					swap(_con[parent], _con[child]);
			//5.孩子节点成为父节点,并重新找左孩子节点
					parent = child;
					child = parent * 2 + 1;
				}
				else
				{
					//父节点大于(小于)孩子节点即可结束
					break;
				}

			//6.一直调整,直到父节点大于(小于)孩子节点或者父节点没有孩子节点了
			}

		}

		void AdjustUp(int child)
		{
			//1.先找到父节点
			int parent = (child - 1) / 2;//公式前面讲过
			while (child > 0)//保证不超过根节点,最后一次循环只能是child为1或2,parent为0
			{
				//2.判断是否父节点小于(大于)孩子节点
				//if(Compare()(_con[parent],_con[child]))//使用匿名对象来判断也可以
				if (_com(_con[parent], _con[child]))//默认是大堆,是判断是否父节点小于孩子节点
				{
					//3.进行交换
					swap(_con[parent], _con[child]);
					//4.调整parent,child,继续向上调整直到不需要调换或者到根节点
					child = parent;//父节点成为孩子节点继续往上调整
					parent = (child - 1) / 2;
				}
				else
				{
					//已经不需要调整了
					break;
				}
			}
		}

	private:
		Container _con;//用来存储完全二叉树
		Compare _com;//用来比较的方法
	};

}

🌸🌸C++priority_queue模拟实现的知识大概就讲到这里啦,博主后续会继续更新更多C++的相关知识,干货满满,如果觉得博主写的还不错的话,希望各位小伙伴不要吝啬手中的三连哦!你们的支持是博主坚持创作的动力!💪💪


http://www.kler.cn/a/515556.html

相关文章:

  • 【深度学习入门】深度学习知识点总结
  • nslookup在内网渗透的使用
  • 正则表达式的艺术:轻松驾驭 Python 的 re 库
  • Redis支持数据类型详解
  • C语言小项目——通讯录
  • 66,【6】buuctf web [HarekazeCTF2019]Avatar Uploader 1
  • linux 下调试 pac1934 电源监控器
  • AutoPrompt框架和实操:如何用AutoPrompt完成电影评论和聊天审核任务?
  • python内置的调试工具-pdb
  • 解决SpringBoot项目启动错误:找不到或无法加载主类
  • 一文大白话讲清楚webpack基本使用——11——chunkIds和runtimeChunk
  • 【玩转全栈】----基于ModelForm完成用户管理页面
  • 作品显示ip属地与定位哪个是真实的
  • 解决因JDK升级导致的`java.nio.file.NoSuchFileException`问题
  • 【K8S问题系列 |19 】如何解决 Pod 无法挂载 PVC问题
  • Python并发编程 07 事件驱动模型、进程切换、进程阻塞、文件描述符、缓存I/O、selectors模块
  • Vue3+Element Plus 实现 el-table 表格组件滚动是否触底监听判断
  • 父级perspective与子元素transform:perspective的区别
  • 在vue3中使用datav完整引入时卡在加载页面的解决方法
  • 【10.2】队列-设计循环队列
  • FFmpeg音视频采集
  • 数据结构——实验二·栈
  • 2025美赛倒计时,数学建模五类模型40+常用算法及算法手册汇总
  • 【2024年华为OD机试】 (E卷,100分) - 整数编码(JavaScriptJava PythonC/C++)
  • 4.C++中的循环语句
  • 【Mac】Python相关知识经验