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

priority_queue的模拟实现

一:需要模拟实现的函数

本博客暂实现以下内容

解释:本篇实现priority_queue的重点在于

①:向上/向下调整函数

优先级队列相较于普通的队列,其区别主要是在 push 和 pop 上,即需要在插入 / 删除数据的同时,增添调整的功能,已达到堆的效果

②:priority_queue是一个适配器类的体现

③:仿函数结合向上/向下调整函数,以达到不修改向上/向下调整函数内部即可分别实现大小堆

仿函数不太懂的先看这篇:仿函数 VS 函数指针实现回调 -CSDN博客

堆排序都不懂的先看这篇:堆排序 _7-3 堆排序-CSDN博客

向上/向下调整复杂度看这篇:向上or向下调整建堆 的时间复杂度_向上调整法的时间复杂度-CSDN博客

二:代码

namespace bit
{

	//仿函数
	// less: 小于的比较
	template<class T>
	struct less
	{
		bool operator()(const T& x, const T& y) const
		{
			return x < y;
		}
	};

	//仿函数
	// greater: 大于的比较
	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:
		// 构造函数1:构造空的优先级队列
		priority_queue()
			: _con()
		{}

		// 构造函数2:迭代器区间构造优先级队列
		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last)
			: _con(first, last)
		{
			int count = _con.size();
			int root = ((count - 2) / 2);
			for (; root >= 0; root--) {
				AdjustDown(root);
			}
		}

		// 向上调整算法 
		void AdjustUp(size_t child) {
			Compare cmpFunc;

			size_t father = (child - 1) / 2;
			while (child > 0)
			{

				if (cmpFunc(_con[father], _con[child])) {
					swap(_con[father], _con[child]);
					child = father;
					father = (child - 1) / 2;
				}

				else
				{
					break;
				}
			}
		}

		// 向下调整算法 
		void AdjustDown(size_t father)
		{
			Compare cmpFunc;

			size_t child = father * 2 + 1; // 默认认为左孩子大
			while (child < _con.size())
			{

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


				if (cmpFunc(_con[father], _con[child]))
				{
					swap(_con[father], _con[child]);
					father = child;
					child = father * 2 + 1;
				}

				else
				{
					break;
				}
			}
		}

		/* 入数据 */
		void push(const T& x)
		{
			_con.push_back(x);
			AdjustUp(_con.size() - 1);
		}

		/* 出数据 */
		void pop()
		{
			assert(!_con.empty());
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();

			AdjustDown(0);
		}

		/* 返回堆顶数据 */
		const T& top()
		{
			return _con[0];
		}

		/* 返回大小 */
		size_t size()
		{
			return _con.size();
		}

		/* 判断是否为空 */
		bool empty()
		{
			return _con.empty();
		}

	private:
		Container _con;//一个容器类的对象

	};

	// 小于比较,搞大堆
	void test_priority_queue1()
	{
		priority_queue<int> pq;
		pq.push(2);
		pq.push(5);
		pq.push(1);
		pq.push(6);
		pq.push(8);
		pq.push(9);
		pq.push(3);

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

	// 大于比较,搞小堆
	void test_priority_queue2()
	{
		priority_queue<int, vector<int>, greater<int>> pq;
		pq.push(2);
		pq.push(5);
		pq.push(1);
		pq.push(6);
		pq.push(8);
		pq.push(9);
		pq.push(3);

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

int main(void)
{
	bit::test_priority_queue1();
	bit::test_priority_queue2();

	return 0;
}

运行结果:

三:代码解释

1. 仿函数(Functor)

仿函数是一个类,重载了 operator(),使得它的实例可以像函数一样被调用。代码中定义了两个仿函数:

  less<T>:用于比较两个值的大小,返回 x < y 的结果。默认情况下,它用于构建大堆(堆顶元素最大)。

  greater<T>:用于比较两个值的大小,返回 x > y 的结果。它用于构建小堆(堆顶元素最小)。

//仿函数
	// less: 小于的比较
	template<class T>
	struct less
	{
		bool operator()(const T& x, const T& y) const
		{
			return x < y;
		}
	};

	//仿函数
	// greater: 大于的比较
	template<class T>
	struct greater
	{
		bool operator()(const T& x, const T& y) const
		{
			return x > y;
		}
	};

2. 优先级队列类 priority_queue

你的 priority_queue 类是一个模板类,支持以下功能:

模板参数

  T:队列中存储的元素类型。

  Container:底层容器类型,默认是 std::vector<T>

  Compare:比较函数对象类型,默认是 less<T>(用于构建大堆)

template<class T, class Container = vector<T>, class Compare = less<T>>
class priority_queue {
    // ...
};
成员变量

  _con:底层容器,用于存储堆中的元素。

  注意:_cmpFunc并不算成员变量,他只是在调整函数中创建的临时对象,用于实现大于比较和小于比较

构造函数

      默认构造函数:创建一个空的优先级队列。

      范围构造函数:通过迭代器范围初始化队列,并调用 AdjustDown 构建堆。

      提示:范围构造函数还能这样写:

template<class InputIterator>
priority_queue(InputIterator first, InputIterator last) 
    : _con(first, last)
{
    int count = _con.size();
    int root = ((count - 2) >> 1);
    for (; root >= 0; root--) {
        AdjustDown(root);
    }
}

解释: 

/ 2可以替换成 >> 1 ,因为对于正整数来说,右移一位(>> 1)和除以 2(/ 2)的结果是相同的。两者都是将数值除以 2 并向下取整。 

堆调整算法
  AdjustUp

  AdjustUp作用:是将新插入的元素从底部逐步向上调整,直到整个堆重新满足堆的性质(大堆或小堆)。

      AdjustUp代码解释:

2. 参数说明

  • child:当前需要调整的节点的索引(通常是新插入元素的索引)。

3. 核心逻辑

(1)计算父节点索引

  • 在完全二叉树中,父节点和子节点的索引关系为:

    • 父节点索引:father = (child - 1) / 2

    • 左孩子索引:child = father * 2 + 1

    • 右孩子索引:child = father * 2 + 2

    因此,father = (child - 1) / 2 用于计算当前节点的父节点索引。

(2)比较父节点和当前节点

  • 使用仿函数 cmpFunc 比较父节点和当前节点:

    • 如果 cmpFunc(_con[father], _con[child]) 返回 true,表示父节点不满足堆的性质,需要交换。

    • 如果返回 false,表示父节点已经满足堆的性质,调整结束。

(3)交换节点

  • 如果父节点不满足堆的性质,交换父节点和当前节点的值。

  • 更新当前节点为父节点,继续向上调整。

(4)终止条件

  • 当 child 为 0(即当前节点已经是根节点)时,调整结束。

  • 如果父节点已经满足堆的性质,调整结束。


  AdjustDown

  AdjustDown作用:

        是将一个元素删除之后,对于剩下元素从根节点逐步向下调整,直到整个堆重新满足堆的性质(大堆或小堆)。(规定:删除指的是删除头结点 要先交换头尾元素 然后缩小堆范围1 再从头向下调整)

  AdjustDown代码解释:

2. 参数说明

  • father:当前需要调整的节点的索引(通常是堆顶元素的索引)。

3. 核心逻辑

(1)初始化

  • child = father * 2 + 1:计算当前节点的左孩子索引。

  • 默认认为左孩子是较大的孩子。

(2)选择较大的孩子

  • 如果右孩子存在(child + 1 < _con.size())且右孩子比左孩子大(cmpFunc(_con[child], _con[child + 1])),则将 child 更新为右孩子的索引。

  • 这样做的目的是确保 child 指向当前节点的较大孩子。

(3)比较父节点和较大的孩子

  • 使用仿函数 cmpFunc 比较父节点和较大的孩子:

    • 如果 cmpFunc(_con[father], _con[child]) 返回 true,表示父节点不满足堆的性质,需要交换。

    • 如果返回 false,表示父节点已经满足堆的性质,调整结束。

(4)交换节点

  • 如果父节点不满足堆的性质,交换父节点和较大的孩子的值。

  • 更新父节点为较大的孩子,继续向下调整。

(5)终止条件

  • 当 child 超出堆的范围(child >= _con.size())时,调整结束。

  • 如果父节点已经满足堆的性质,调整结束。

(6)代码需要注意的细节:

        1:向下调整的极限是child位于最后一个元素,即下标达到最大值n-1,(n就是size)(比如6个元素,下标最大只能取到5),所以child只能小于n,不可能大于或等于n

        2:但是在确定孩子中较小值的时候,会用到a[child+1,]来判断,所以为了避免超出范围,确定范围的这个if应该是&&,左面确保child+1<n,左面成立,才会执行&&右面的比较,否则会越界访问

        3:不能为了将就第一个if,而在while的条件直接 child<n-1,因为如果child的下标会更改为最后一个元素,此时child 的值为n-1,此时会因为错误的判断,而不进入循环判断,应该是先进入循环,不进去刚才的if,最后再让那一个孩子和他比较。

其余函数

push:插入一个新元素,并调用 AdjustUp 维护堆的性质。

pop:删除堆顶元素,并调用 AdjustDown 维护堆的性质。

top:返回堆顶元素。

size:返回队列中元素的数量。

empty:判断队列是否为空。

解释:

值得注意的是,返回堆顶数据的 top 接口,我们用了 const 引用返回。

Q:为什么这里要用引用?为什么还要加个 const?

① 考虑到如果这里 T 是 string,甚至是更大的对象,传值返回就有一次拷贝构造,代价就大了。

② 避免了随随便便修改堆顶的数据,

其余过于简单 不再赘述

相关文章:

  • 如何使用logminer
  • 剑指小米特斯拉:秦L EV上市11.98万起
  • BP神经网络+NSGAII算法(保真)
  • 【F#入门】第四讲 函数,管道与组合
  • dnf命令详解
  • hackmyvm-lookup
  • Linux中查找标准库函数的定义
  • MongoDB未授权访问漏洞
  • 个人博客系统 --- 测试报告
  • 【js逆向】某酒店模拟登录
  • 【蓝桥杯】真题 2386染色时间 (优先队列BFS)
  • 智慧教育云平台支持下的学生自主学习能力培养策略研究
  • dfs刷题矩阵搜索问题
  • 深入理解Java虚拟机(学习笔记)
  • 开源视频剪辑工具,无损编辑更高效
  • 车道保持中车道线识别
  • XSS 攻击向量与绕过技巧
  • OpenCV的基本用法全解析
  • 【深度学习入门_机器学习理论】梯度提升决策树(GBDT)
  • C语言-状态模式详解与实践 - OTA升级状态机
  • 菏泽家长“付费查成绩”风波调查:免费功能被误读
  • 铁路12306回应“五一前大量放票”传闻:个别方向和区段出现新增票额,均即时进入系统重新发售
  • 2025年第一批“闯中人”已经准备好了
  • 特朗普执政百日集会吹嘘政绩,美国消费者信心指数跌至疫情以来最低
  • 浦发银行一季度净利175.98亿增1.02%,不良率微降
  • 东风着陆场做好各项搜救准备,迎接神舟十九号航天员天外归来