C++ STL详解(五) -------- priority_queue

目录

1. priority_queue介绍

2.堆的向上和向下调整算法

(1)堆的向上调整算法

(2)堆的向下调整算法

3.priority_queue模拟实现 


                

 

1. priority_queue介绍

①. 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。  

②. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。

③. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。

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

  • empty():检测容器是否为空
  • size():返回容器中有效元素个数
  • front():返回容器中第一个元素的引用
  • push_back():在容器尾部插入元素
  • pop_back():删除容器尾部元素

⑤. 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。

⑥. 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作。

        

        

2.堆的向上和向下调整算法

  • priority_queue的底层实际上就是堆结构

(1)堆的向上调整算法

调整的基本思想如下:

  • 将目标结点与其父结点进行比较。
  • 若目标结点的值比父结点的值大,则交换目标结点与其父结点的位置,并将原目标结点的父结点当作新的目标结点继续进行向上调整;若目标结点的值比其父结点的值小,则停止向上调整,此时该树已经是大堆了。 
  • 以大堆为例,堆的向上调整算法就是在大堆的末尾插入一个数据后,经过一系列的调整,使其仍然是一个大堆
  • 现在有一些数据(49  28  57  34  53  84  83  74  52),经过向上调整之后的顺序是 (84  74  83   53  34  49  57  28  52) 
  • 现在我们要插入一个数据80,下面是调整过程

C++ STL详解(五) -------- priority_queue_第1张图片

 代码实现:

//堆的向上调整(大堆)
void AdjustUp(vector& v, int child)
{
	size_t parent = (child - 1) / 2; //通过child计算parent的下标

	while (child > 0)//调整到根结点的位置截止
	{
		if (v[parent] < v[child])//孩子结点的值大于父结点的值
		{
			//将父结点与孩子结点交换
			swap(v[child], v[parent]);

			//继续向上进行调整
			child = parent;
			parent = (child - 1) / 2;
		}
		else//已成堆
		{
			break;
		}
	}
}

                         

(2)堆的向下调整算法

  • 以大堆为例,使用堆的向下调整算法有一个前提,就是待向下调整的结点的左子树和右子树必须都为大堆。

调整的基本思想如下:

  • 将目标结点与其较大的子结点进行比较。
  • 若目标结点的值比其较大的子结点的值小,则交换目标结点与其较大的子结点的位置,并将原目标结点的较大子结点当作新的目标结点继续进行向下调整;若目标结点的值比其较大子结点的值大,则停止向下调整,此时该树已经是大堆了。 
  • 现在我们要获取priority_queue 的堆顶元素( 84 ),然后pop,此时堆需要重新调整成大堆,
  • 调整过程如下: 先返回堆顶元素,然后将vector 中的第一个元素和最后一个元素的值交换, 然后vector 进行 pop_back().

C++ STL详解(五) -------- priority_queue_第2张图片

 代码实现:

//堆的向下调整(大堆)
void AdjustDown(vector& v, int n, int parent)
{
	//child记录左右孩子中值较大的孩子的下标
	int child = 2 * parent + 1;//先默认其左孩子的值较大

	while (child < n) //child不能越界
	{
		if (child + 1 < n && v[child] < v[child + 1])//右孩子存在并且右孩子比左孩子还大
		{
			child++;//较大的孩子改为右孩子
		}

		if (v[parent] < v[child])//左右孩子中较大孩子的值比父结点还大
		{
			//将父结点与较小的子结点交换
			swap(v[child], v[parent]);

			//继续向下进行调整
			parent = child;
			child = 2 * parent + 1;
		}
		else//已成堆
		{
			break;
		}
	}
}

                 

                        

3.priority_queue模拟实现 

namespace XM //防止命名冲突
{
	//比较方式(使内部结构为大堆)
	template
	struct less
	{
		bool operator()(const T& x, const T& y)
		{
			return x < y;
		}
	};

	//比较方式(使内部结构为小堆)
	template
	struct greater
	{
		bool operator()(const T& x, const T& y)
		{
			return x > y;
		}
	};


	//优先级队列的模拟实现
	template, class Compare = less>
	class priority_queue
	{
	public:
		//堆的向上调整
		void AdjustUp(int child)
		{
			int parent = (child - 1) / 2; //通过child计算parent的下标
			while (child > 0)//调整到根结点的位置截止
			{
				if (_comp(_con[parent], _con[child]))//通过所给比较方式确定是否需要交换结点位置
				{
					//将父结点与孩子结点交换
					swap(_con[child], _con[parent]);

					//继续向上进行调整
					child = parent;
					parent = (child - 1) / 2;
				}
				else//已成堆
				{
					break;
				}
			}
		}

		//插入元素到队尾(并排序)
		void push(const T& x)
		{
			_con.push_back(x);
			AdjustUp(_con.size() - 1); //将最后一个元素进行一次向上调整
		}

		//堆的向下调整
		void AdjustDown(int n, int parent)
		{
			int child = 2 * parent + 1;
			while (child < n)
			{
				if (child + 1 < n&&_comp(_con[child], _con[child + 1]))
				{
					child++;
				}

				if (_comp(_con[parent], _con[child]))//通过所给比较方式确定是否需要交换结点位置
				{
					//将父结点与孩子结点交换
					swap(_con[child], _con[parent]);

					//继续向下进行调整
					parent = child;
					child = 2 * parent + 1;
				}
				else//已成堆
				{
					break;
				}
			}
		}

		//弹出队头元素(堆顶元素)
		void pop()
		{
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			AdjustDown(_con.size(), 0); //将下标为0个元素进行一次向下调整
		}

		//访问队头元素(堆顶元素)
		T& top()
		{
			return _con[0];
		}
		const T& top() const
		{
			return _con[0];
		}

		//获取队列中有效元素个数
		size_t size() const
		{
			return _con.size();
		}

		//判断队列是否为空
		bool empty() const
		{
			return _con.empty();
		}
	private:
		Container _con; //底层容器
		Compare _comp; //比较方式
	};
}

(1)关于priority_queue底层堆的调整

  • push : 在容器尾部插入元素后进行一次向上调整算法
  • pop : 将容器头部和尾部元素交换,再将尾部元素删除,最后从根结点开始进行一次向下调整算法
  • 通过push 和 pop 函数底层堆不断进行调整始终保持堆的结构 ;每插入 / 删除 一个元素都要进行堆的调整 

(2)仿函数实现比较方式

  • 为什么要用仿函数 , 而不用函数指针 , 因为函数指针太复杂了,用起来不好
  • 通过重载operator()方式实现仿函数
  • 默认情况下,priority_queue是大堆
  • 如果在priority_queue中放自定义类型的数据,用户需要在自定义类型中提供> 或者< 的重载。

你可能感兴趣的:(C++,c++,STL)