【STL】优先级队列剖析及模拟实现

作者阿润菜菜
📖专栏C++



什么是优先级队列,它与普通队列有什么区别和优势

优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的(默认大堆)。优先级队列的内部实现通常是用来维护元素的优先级,使得每次出队的元素都是当前队列中优先级最高的元素。

优先级队列与普通队列的区别和优势有以下几点:

  • 优先级队列可以根据元素的大小、时间、重要性等因素来定义优先级,使得出队顺序更加灵活和合理。
  • 优先级队列可以高效地找到最大或最小的元素,而不需要对整个队列进行排序。
  • 优先级队列可以应用于一些需要动态调整顺序的场景,如任务调度、事件处理、数据压缩等。

底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭
代器访问,并支持以下操作:

empty():检测容器是否为空

  • size():返回容器中有效元素个数
  • front():返回容器中第一个元素的引用
  • push_back():在容器尾部插入元素
  • top( ) 返回优先级队列中最大(最小元素),即堆顶元素
  • pop_back():删除容器尾部元素
  1. 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。
  2. 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作。
  3. 优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。注意:默认情况下priority_queue是大堆

优先级队列的常用操作和方法,如入队、出队、获取最高优先级元素等

在这里插入图片描述

优先级队列的常用操作和方法有以下几种:

  • 入队:将一个元素和它的优先级插入到优先级队列中,通常用enqueue或add方法表示。
    – 例如,pq.enqueue(‘a’, 1)或pq.add(‘a’, 1)表示将元素’a’和它的优先级1插入到优先级队列pq中。
  • 出队:将优先级队列中优先级最高(或最低)的元素移除并返回,通常用dequeue或pop方法表示。
    –例如,pq.dequeue()或pq.pop()表示将优先级队列pq中优先级最高的元素移除并返回。
  • 获取最高优先级元素:查看优先级队列中优先级最高(或最低)的元素,但不移除它,通常用front、peek或top方法表示。
    –例如,pq.front()、pq.peek()或pq.top()表示查看优先级队列pq中优先级最高的元素,但不移除它。
  • 判断是否为空:检查优先级队列是否为空,通常用isEmpty或empty方法表示。
    –例如,pq.isEmpty()或pq.empty()表示检查优先级队列pq是否为空。
  • 获取元素个数:返回优先级队列中元素的个数,通常用size方法表示。
    –例如,pq.size()表示返回优先级队列pq中元素的个数。
  • 转换为字符串:以字符串的形式展示优先级队列中的所有元素和它们的优先级,通常用toString方法表示。
    –例如,pq.toString()表示以字符串的形式展示优先级队列pq中的所有元素和它们的优先级。

priority_queue的模拟实现

优先级队列的内部实现原理,如何利用堆来维护元素的优先级

在这里插入图片描述

  1. 优先级队列的内部实现原理是利用一种特殊的数据结构,叫做堆。堆是一种完全二叉树,它的每个节点都小于(或大于)它的所有后代,这样就可以保证根节点是最小(或最大)的元素。

  2. 优先级队列可以用数组来存储堆的结构,数组中第一个元素是根节点,然后按照层次顺序依次存储其他节点。数组中下标为k的元素的父节点的下标为k/2,其子节点的下标为2k和2k+1。

  3. 优先级队列的插入和删除操作都需要维护堆的有序性。插入操作是将元素追加到数组的尾部,然后执行上浮操作,即递归地将元素与其父节点比较并交换,直到找到合适的位置或者到达根节点。删除操作是将数组中第一个元素(即最小或最大元素)移除,并将数组中最后一个元素移到第一个位置,然后执行下沉操作,即递归地将元素与其子节点比较并交换,直到找到合适的位置或者到达叶子节点。

优先级队列的常用接口实现

#pragma once

#include <iostream>
using namespace std;

#include <vector>
// priority_queue--->堆
namespace HUE
{
	template<class T>
	struct less
	{
		bool operator()(const T& left, const T& right)
		{
			return left < right;
		}
	};

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

	template<class T, class Container = std::vector<T>, class Compare = less<T>>
	class priority_queue
	{
	public:
		// 创造空的优先级队列
		priority_queue() : c() {}

		template<class Iterator>
		priority_queue(Iterator first, Iterator last)
			: c(first, last)
		{
			// 将c中的元素调整成堆的结构
			int count = c.size();
			int root = ((count - 2) >> 1);
			for (; root >= 0; root--)
				AdjustDown(root);
		}

		void push(const T& data)
		{
			c.push_back(data);
			AdjustUP(c.size() - 1);
		}

		void pop()
		{
			if (empty())
				return;

			swap(c.front(), c.back());
			c.pop_back();
			AdjustDown(0);
		}

		size_t size()const
		{
			return c.size();
		}

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

		// 堆顶元素不允许修改,因为:堆顶元素修改可以会破坏堆的特性
		const T& top()const
		{
			return c.front();
		}
	private:
		// 向上调整
		void AdjustUP(int child)
		{
			int parent = ((child - 1) >> 1);
			while (child)
			{
				if (Compare()(c[parent], c[child]))
				{
					swap(c[child], c[parent]);
					child = parent;
					parent = ((child - 1) >> 1);
				}
				else
				{
					return;
				}
			}
		}

		// 向下调整
		void AdjustDown(int parent)
		{
			size_t child = parent * 2 + 1;
			while (child < c.size())
			{
				// 找以parent为根的较大的孩子
				if (child + 1 < c.size() && Compare()(c[child], c[child + 1]))
					child += 1;

				// 检测双亲是否满足情况
				if (Compare()(c[parent], c[child]))
				{
					swap(c[child], c[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
					return;
			}
		}
	private:
		Container c;
	};
}

void TestQueuePriority()
{
	HUE::priority_queue<int> q1;
	q1.push(5);
	q1.push(1);
	q1.push(4);
	q1.push(2);
	q1.push(3);
	q1.push(6);
	cout << q1.top() << endl;

	q1.pop();
	q1.pop();
	cout << q1.top() << endl;

	vector<int> v{ 5,1,4,2,3,6 };
	HUE::priority_queue<int, vector<int>, HUE::greater<int>> q2(v.begin(), v.end());
	cout << q2.top() << endl;

	q2.pop();
	q2.pop();
	cout << q2.top() << endl;
}
  1. 优先级队列是一个适配器,底层传入vector或者dequee都没有影响
  2. 大堆怎么变小堆?— 利用仿函数 — 怎么实现? 泛型编程,模板的力量— 实例化时传给什么类就是比较什么(传一个类,整个类都可以使用 ),就像是一个开关,你打开什么由你自己决定去控制

优先级队列的应用场景和示例,如任务调度、事件处理、数据压缩等

  • 任务调度:优先级队列可以用来实现一个任务调度系统,其中每个任务都有一个优先级,优先级高的任务先被执行。例如,操作系统中的进程调度,网络中的数据包传输,医院中的急诊病人处理等都可以使用优先级队列来实现。
  • 事件处理:优先级队列可以用来实现一个事件处理系统,其中每个事件都有一个发生时间,发生时间早的事件先被处理。例如,模拟器中的事件驱动模拟,游戏中的动画效果,日程管理软件中的提醒功能等都可以使用优先级队列来实现。
  • 数据压缩:优先级队列可以用来实现一种数据压缩算法,称为哈夫曼编码(Huffman Coding),其中每个字符都有一个出现频率,出现频率高的字符用较短的编码,出现频率低的字符用较长的编码。例如,文本文件,图片文件,音频文件等都可以使用哈夫曼编码来压缩。

优先级队列的优缺点和改进方向,如如何提高效率、节省空间、扩展功能等

优先级队列的优缺点和改进方向有以下几种:

  • 优点:优先级队列可以高效地实现一些需要不断地取出最大或最小元素的场景,例如任务调度,事件处理,数据压缩等。优先级队列的插入和删除操作的时间复杂度都是O(logn),比线性结构要快得多。
  • 缺点:优先级队列的空间复杂度是O(n),比线性结构要高一些。优先级队列的实现通常基于二叉堆,而二叉堆的结构可能会导致内存碎片和缓存不命中。优先级队列的排序规则可能不够灵活,例如不能同时考虑多个因素的优先级。
  • 改进方向:优先级队列的实现可以使用其他的数据结构,例如斐波那契堆,配对堆,二项堆等,这些数据结构可以提高某些操作的效率,例如合并两个优先级队列。优先级队列的排序规则可以使用自定义的比较器或者复合键来实现更多样化的需求。优先级队列的空间占用可以通过动态调整容量或者使用压缩技术来减少。

参考:Priority Queue
在这里插入图片描述

  • 16
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吉始

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值