【STL】priority_queue模拟实现

1 仿函数

默认比较方式less:
在这里插入图片描述
在这里插入图片描述

显式修改比较方式为greater:
在这里插入图片描述
在这里插入图片描述

1. 默认比较方式是less,建大堆,堆顶元素是最大的,遍历顺序就是从大到小
2. 另一个比较方式是greater,建小堆,堆顶元素是最小的,遍历顺序就是从小到大

2 模拟实现

priority_queue的底层就是 ,所以模拟实现最重要的是实现堆的调整。

2.1 堆的向下调整算法(以大堆为例)

2.1.1 向下调整算法流程

在大堆中,堆的向下调整算法,必须保证以当前节点为根节点的二叉树为大堆。

以下面这个堆举例:

在这里插入图片描述
因为这 整个堆是无序的,所以需要 将下面的那个堆建成大堆
在这里插入图片描述
经过上面的调整,图中红色方框的堆已经变成大堆了。
在这里插入图片描述

接下来要调整以4为根节点的堆。
在这里插入图片描述
经过上面的调整,图中红色方框的堆已经变成大堆了。
在这里插入图片描述
接下来要调整以1为根节点的堆。
在这里插入图片描述
在这里插入图片描述

刚才调整的是以6为根节点的堆,所以绿色框中的堆变成了一个大堆。
但是由于之前以9为根节点的堆也调整成了一个大堆,所以红色框中的堆也是一个大堆。
在这里插入图片描述
接下来要调整以3为根节点的堆。
在这里插入图片描述
在这里插入图片描述
到这里,全部调整完毕,整个堆都变成了一个大堆。

调整的全部过程如下:
在这里插入图片描述

2.1.2 向下调整代码

void AdjustDown(int parent)
{
	size_t child = parent * 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;
		}
	}
}

注意:向下调整是从父节点开始调整,所以传值的时候需要注意 最小的parent应该是最后一个结点下标的父节点

1. 父节点如何确定?

因为堆是二叉树,所以父节点的确定就是 parent = (child - 1) / 2;

2. 子节点如何确定?

child = parent * 2 + 1;

下面是迭代器构造向下调整建堆的过程:

priority_queue(InputIterator first, InputIterator last, const Compare& comFunc = Compare())
{
	//1.将数据放入堆中
	while (first != last)
	{
		_con.push_back(*first);
		++first;
	}

	//2.建堆
	for (int i = (_con.size() - 1 - 1) / 2; i >= 0; -- i)
	{
		//传入下标, 进行向下调整  -- 向下调整是传入parent

		AdjustDown(i);
	}
}

3. 为什么for (int i = (_con.size() - 1 - 1) / 2; i >= 0; -- i)i = (_con.size() - 1 - 1) / 2

因为parent = (child - 1) / 2;
_con.size() - 1表示child的下标,将_con.size() - 1看作是child就行了.

4. 下面这句代码是什么意思?

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

找到孩子结点中大的那个进行调整建大堆

5. child + 1 < _con.size() && _con[child] < _con[child + 1]中?两个表达式可以交换位置吗?

不能,这样写是为了防止child + 1越界,如果先执行_con[child] < _con[child + 1]可能导致越界访问。

6.如果想要通过向下调整算法建小堆该怎么写代码?

只需要将比较过程中的小于改为大于即可。这样是写死的方法, 后面可以通过调用不同的仿函数来实现不同的建堆

2.2 堆的向上调整算法(以大堆为例)

假设我们现在已经有一个大堆, 我们需要在堆的末尾插入数据,然后对其进行调整,使其仍然保持大堆的结构。

1. 将要插入的值和父节点的值进行比较
2. 如果插入的值比父节点的值大,则交换位置。
3. 继续比较当前节点和新的父节点的值;如果大,则交换位置。

建大堆的代码:

void AdjustUp(int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		//把大的往上调
		if (_con[parent] < _con[child])
		{
			std::swap(_con[parent], _con[child]);
			//child往上调
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

2.3 push(const T& x)

进入堆的流程:
1. 在最后插入x
2. 利用向上调整算法将x调整到合适的位置

		void push(const T& x)
		{
			//1.将x放入到末尾
			_con.push_back(x);

			//2.向上调整 -- 最后一个元素
			AdjustUp(_con.size() - 1);
		}

2.4 pop()

因为出堆是出的堆顶元素,也就是下标为0的元素。 如果直接删掉堆顶元素,后面就不好调整了。所以要先将堆顶元素和最后一个元素 交换,然后出掉交换后的堆顶元素,再对交换到堆顶的最后一个元素进行 向下调整

出堆的流程:
1. 堆顶元素和堆最后一个元素交换
2. 出掉交换后的堆顶元素
3. 将交换后的最后一个元素向下调整到合适的位置。

void pop()
{
	if (empty())
		return;
	//1.将堆顶元素和堆底元素交换
	std::swap(_con[0], _con[_con.size() - 1]);
	_con.pop_back();
	AdjustDown(0);
}

2.5 仿函数修改调整算法

之前的向上调整和向下调整都是写死的创建大堆,如果想要创建小堆,要自己去改内部的代码,这是很不好的。所以我们需要添加仿函数来控制建大堆还是小堆。

7.使用仿函数的原理是什么?

由用户显式的传递一个仿函数(less或者greater),然后根据传入的内容去调用不同的仿函数。
仿函数的本质是类对象重载了operator(),就让调用类像调用函数一样,所以称为仿函数。

经过仿函数修改的AdjustDown代码:

void AdjustDown(int parent)
{
	size_t child = parent * 2 + 1;

	//找到左右孩子中大的那一个
	while (child < _con.size())
	{
		//if (child + 1 < _con.size() && _con[child] < _con[child + 1])
		if (child + 1 < _con.size() && _comFunc(_con[child], _con[child + 1]))
		{
			++child;
		}

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

修改后的AdjustUp代码

void AdjustUp(int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		//把大的往上调
		//if (_con[parent] < _con[child])
		if (_comFunc(_con[parent], _con[child]))
		{
			std::swap(_con[parent], _con[child]);
			//child往上调
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

修改后的测试:
传递less,建大堆
在这里插入图片描述
传递greater,建小堆
在这里插入图片描述

2.3 全部代码

priority_queue.h文件

#pragma once
#include <iostream>
#include <vector>
#include <queue>

using std::cout;
using std::endl;
using std::istream;
using std::ostream;

namespace zyy
{
	//建大堆
	template <class T>
	class less
	{
	public:
		bool operator()(const T& x, const T& y) const
		{
			return x < y;
		}
	};

	//建小堆
	template <class T>
	class greater
	{
	public:
		bool operator()(const T& x, const T& y) const
		{
			return x > y;
		}
	};
	template <class T, class Container = std::vector<T>, class Compare = less<T>>
	class priority_queue
	{
	public:
		//创造一个空的priority_queue,利用缺省参数,如果不传,就是默认的less了
		priority_queue(const Compare& comFunc = Compare())
			:_comFunc(comFunc)
		{}

		//构造函数 -- 迭代器构造
		template<class InputIterator>
		priority_queue(InputIterator first, InputIterator last, const Compare& comFunc = Compare())
		{
			//1.将数据放入堆中
			while (first != last)
			{
				_con.push_back(*first);
				++first;
			}

			//2.建堆
			for (int i = (_con.size() - 1 - 1) / 2; i >= 0; -- i)
			{
				//传入下标, 进行向下调整  -- 向下调整是传入parent

				AdjustDown(i);
			}
		}

		void AdjustDown(int parent)
		{
			size_t child = parent * 2 + 1;

			//找到左右孩子中大的那一个
			while (child < _con.size())
			{
				//if (child + 1 < _con.size() && _con[child] < _con[child + 1])
				if (child + 1 < _con.size() && _comFunc(_con[child], _con[child + 1]))
				{
					++child;
				}

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

		//传入的是child,一般是插入进行向上调整
		void AdjustUp(int child)
		{
			int parent = (child - 1) / 2;
			while (child > 0)
			{
				//把大的往上调
				//if (_con[parent] < _con[child])
				if (_comFunc(_con[parent], _con[child]))
				{
					std::swap(_con[parent], _con[child]);
					//child往上调
					child = parent;
					parent = (child - 1) / 2;
				}
				else
				{
					break;
				}
			}
		}

		void push(const T& x)
		{
			//1.将x放入到末尾
			_con.push_back(x);

			//2.向上调整 -- 最后一个元素
			AdjustUp(_con.size() - 1);
		}

		void pop()
		{
			if (empty())
				return;
			//1.将堆顶元素和堆底元素交换
			std::swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			AdjustDown(0);
		}

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

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

		T& top()
		{
			return _con[0];
		}
	private:
		Container _con;   //使用的vector的底层结构
		Compare _comFunc; //比较方式
	};
};

test-priority_queue.cpp文件

#include"priority_queue.h"

using namespace zyy;

//void test1()
//{
//	priority_queue<int> pq;
//	pq.push(4);
//	pq.push(1);
//	pq.push(5);
//	pq.push(6);
//	pq.push(2);
//
//	cout << "pq type:" << typeid(pq).name() << endl;
//	for (int i = 0; i < 5; ++i)
//	{
//		cout << pq.top() << " ";
//		pq.pop();
//	}
//	cout << endl;
//}
//
//void test2()
//{
//	priority_queue<int, vector<int>, greater<int>> pq;
//	pq.push(4);
//	pq.push(1);
//	pq.push(5);
//	pq.push(6);
//	pq.push(2);
//
//	cout << "pq type:" << typeid(pq).name() << endl;
//	for (int i = 0; i < 5; ++i)
//	{
//		cout << pq.top() << " ";
//		pq.pop();
//	}
//	cout << endl;
//}

void test3()
{
	std::vector<int> vec = { 3, 1, 4, 1, 5, 9, 2, 6, 5 };
	priority_queue<int> pq(vec.begin(), vec.end());
	cout << endl;
}

void test4()
{
	priority_queue<int, std::vector<int>, greater<int>> pq;
	pq.push(3);
	pq.push(1);
	pq.push(4);
	pq.push(1);
	pq.push(5);
	pq.push(9);
	pq.push(2);
	pq.push(6);
	pq.push(5);

	for (int i = 0; i < 9; ++i)
	{
		cout << pq.top() << " ";
		pq.pop();
	}
	cout << endl;

}
int main()
{
	test4();
	return 0;
}

代码链接:https://github.com/gaogo21/data-structure/tree/main/Queue/Queue
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值