STL容器适配器之priority_queue的基本用法及模拟实现

1.priority_queue概述

priority_queue是一个拥有权值观念的queue,它允许加入新元素、移除旧元素、审视元素值等功能。由于这是一个queue,所以只允许在底端加入元素,并从顶端取出元素,除此之外别无其他存取元素的途径。

priority_queue带有权值观念,其内的元素并非依照被推入的次序排列,而是自动依照元素的权值排列(通常权值以实值表示)。权值最高者,排在最前面。

缺省情况下priority_queue系利用一个大堆完成,该大堆是一个一个以vector表现的完全二叉树。大堆可以满足priority_queue所需要的“依权值高低自动递减排序”的特性。

2.priority_queue C++标准库的基本用法

//priority_queue C++标准库的基本用法
void test_priority_queue()
{
	std::priority_queue<int> mypq;

	//1、push(val)--在优先级队列中插入元素val
	for (int i = 0; i < 10; ++i)
		mypq.push(i);

	//2、empty()--检测优先级队列是否为空,是返回true,否则返回false
	//3、删除优先级队列中最大(最小)元素,即堆顶元素
	while (!mypq.empty())
	{
		std::cout << mypq.top() << " ";
		mypq.pop();
	}
	std::cout << std::endl;
}

3.heap

3.1 heap概述

heap并不归属于STL容器组件,priority_queue允许用户以任何次序将任何元素推入容器中,但取出时一定是从优先权最高(也就是数值最高)的元素开始取。大根堆正是具有这样的特性,适合作为priority_queue底层机制。关于完全二叉树以及堆的详细介绍可以参考二叉树详解堆的介绍这两篇文章。

堆(heap)是一种特殊的额完全二叉树,它满足下面的性质:

(1)结构性质:堆是一个完全二叉树,这意味着除了最后一层外,每一层都是完全填满的,而最后一层的节点则京可能的集中在左边。

(2)堆性质:在一个最大堆(max heap)中,每一个节点的值都大于或等于其子节点的值,根节点的值是堆中的最大值。相反,在一个最小堆(min heap)中,每个节点的值都小于或等于其子结点的值,根节点的值是堆中的最小值。

如果使用list作为priority_queue的底层机制,元素插入操作是的时间复杂度是O(1)。但是要找到list中的极值,却需要遍历整个链表。

3.2 priority_queue的实现

3.2.1 push()

为了满足二叉树的条件,新加入的元素一定要放在最下一层作为叶节点,并填补在由左至右的第一个空格,也就是把新元素插入在底层vector的end()处。新元素是否适合于其现有位置呢?为了满足max-heap的条件(每个节点的键值都大于或等于其子节点键值)。此时,我们执行一个堆的向上调整程序:将新节点拿来与其父节点比较,如果其键值(key)比父节点大,就父子对换位置。如此一直上溯,直到不需要对换或直到根节点为止,如下图所示。

3.2.1.1 向上调整算法的代码实现

写法1:

//向上调整算法--写法1
void AdjustUp_1(int child)
{
	int parent = (child - 1) / 2;

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

写法2:

//向上调整算法--写法2
void AdjustUp_2()
{
	int child = _con.size()-1;

	while (child > 0)
	{
		int parent = (child - 1) / 2;
		if (_con[child] > _con[parent])
		{
			std::swap(_con[child], _con[parent]);
			child = parent;
		}
		else
		{
			break;
		}
	}
}
3.2.1.2 push的实现
//3、将元素添加到优先队列中
void push(const T& val)
{
	_con.push_back(val);
	//AdjustUp_1(_con.size() - 1);
	AdjustUp_2();
}

3.2.2 pop()

队头数据出队列,假如优先队列是降序排列,队头数据出队列即为删除大根堆堆顶数据(根节点)。如果直接pop操作取走根节点,就破坏了堆原有的结构。此时,将堆顶的数据与堆中最后一个元素进行交换。然后,删除最后一个元素,再进行一次向下调整建堆。

3.2.2.1 向下调整算法的代码实现

写法1:

//向下调整算法--写法1
void AdjustDown_1(int root)
{
	int parent = root;
	int child = root * 2 + 1;//默认左孩子节点的数值大于右孩子节点的数值

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

		if (_con[parent] < _con[child])
		{
			std::swap(_con[parent], _con[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

写法2:

//向下调整算法--写法2
void AdjustDown_2()
{
	int parent = 0;
	int size = _con.size();

	while (true)
	{
		int leftChild = 2 * parent + 1;
		int rightChild = 2 * parent + 2;
		int largest = parent;

		if (leftChild < size && _con[largest] < _con[leftChild])
			largest = leftChild;

		if (rightChild < size && _con[largest] < _con[rightChild])
			largest = rightChild;

		if (largest != parent)
		{
			std::swap(_con[parent], _con[largest]);
			parent = largest;
		}
		else
		{
			break;
		}
	}
}
3.2.2.2 pop的实现
//4、弹出优先队列中的最大元素
void pop()
{
	if (!empty())
	{
		//1、先将堆顶元素与堆数组中末尾的元素进行交换
		std::swap(_con[0], _con[_con.size() - 1]);
		//2、此时,弹出堆数组末尾的元素,即堆中最大的元素
		_con.pop_back();
		//3、使用向下调整算法,调整建堆
		//AdjustDown_1(0);
		AdjustDown_2();
	}
	else
	{
		throw std::runtime_error("Priority queue is empty.");
	}
}

4.priority_queue的实现

//实现priority_queue
template<class T, class Container=std::vector<T>>
class MyPriorityQueue
{
public:
	//1、默认构造函数
	MyPriorityQueue() {}

	//2、构造函数,可以指定底层容器
	MyPriorityQueue(const Container& c)
		:_con(c)
	{
		//std::make_heap(_con.begin(), _con.end());
		//借助向下调整算法,将底层容器调整成堆
		//使用方法1的调整方式--从第一个非叶子结点开始调整
		for (int i = (_con.size() - 1) / 2; i >= 0; --i)
			AdjustDown_1(i);

		//使用方法2的调整方式--先调整左右子树其中的一个
		//for (int i = (_con.size() / 2) - 1; i >= 0; --i)
			//AdjustDown_2();
	}

	//3、将元素添加到优先队列中
	void push(const T& val)
	{
		_con.push_back(val);
		//std::push_heap(_con.begin(), _con.end());
		//AdjustUp_1(_con.size() - 1);
		AdjustUp_2();
	}

	//4、弹出优先队列中的最大元素
	void pop()
	{
		if (!empty())
		{
			//std::pop_heap(_con.begin(), _con.end());
			//1、先将堆顶元素与堆数组中末尾的元素进行交换
			std::swap(_con[0], _con[_con.size() - 1]);
			//2、此时,弹出堆数组末尾的元素,即堆中最大的元素
			_con.pop_back();
			//3、使用向下调整算法,调整建堆
			//AdjustDown_1(0);
			AdjustDown_2();
		}
		else
		{
			throw std::runtime_error("Priority queue is empty.");
		}
	}

	//5、访问优先队列中的最大元素
	T& top()
	{
		if (!empty())
		{
			return _con[0];
		}
		else
		{
			throw std::runtime_error("Priority queue is empty.");
		}
	}

	//6、检查优先队列是否为空
	bool empty() const
	{
		return _con.empty();
	}
	
	//7、返回优先队列的大小
	size_t size() const
	{
		return _con.size();
	}

private:
	Container _con;//使用底层元素存储优先队列的元素

	//向上调整算法--写法1
	void AdjustUp_1(int child)
	{
		int parent = (child - 1) / 2;

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

	//向上调整算法--写法2
	void AdjustUp_2()
	{
		int child = _con.size()-1;

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

	//向下调整算法--写法1
	void AdjustDown_1(int root)
	{
		int parent = root;
		int child = root * 2 + 1;//默认左孩子节点的数值大于右孩子节点的数值

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

			if (_con[parent] < _con[child])
			{
				std::swap(_con[parent], _con[child]);
				parent = child;
				child = parent * 2 + 1;
			}
			else
			{
				break;
			}
		}
	}

	//向下调整算法--写法2
	void AdjustDown_2()
	{
		int parent = 0;
		int size = _con.size();

		while (true)
		{
			int leftChild = 2 * parent + 1;
			int rightChild = 2 * parent + 2;
			int largest = parent;

			if (leftChild < size && _con[largest] < _con[leftChild])
				largest = leftChild;

			if (rightChild < size && _con[largest] < _con[rightChild])
				largest = rightChild;

			if (largest != parent)
			{
				std::swap(_con[parent], _con[largest]);
				parent = largest;
			}
			else
			{
				break;
			}
		}
	}
};

完整代码可参考:优先队列的使用以及实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值