[c++进阶(十)]priority_queue的模拟实现

1.前言

本章重点

本节着重讲解优先级队列的底层原理以及相关接口的模拟实现

2.什么是priority_queue

优先级队列默认的情况下是大堆。

优先级队列有三个函数模版,第一个是队列存储的类型,第二个是优先级队列底层使用的容器,第三个是仿函数,主要用来决定优先级队列里面谁的优先级更大,大的就排堆顶。

总结一下到底什么是优先级队列呢?

优先级队列简单来说就是一棵二叉树,但是这棵二叉树又被一定的条件约束,这个约束就是仿函数,也就是说二叉树的堆顶是由谁排。并且这个优先级队列可以理解成为尾进,头出的队列,即使要删除堆顶元素,也要把堆顶元素和尾部元素互换,再进行删除。

3.priority_queue的使用

优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue。

3.1 priority_queue的构造

1.空构造

#include<iostream>
#include<queue>//队列
#include<vector>//数组
#include<functional>//比较符号,less 或 greater
 
using namespace std;
 
int main()
{
	priority_queue<int> pq1;//构造一个空的优先级队列
}

2.迭代器区间进行构造

    vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
 
	priority_queue<int> pq2(v.begin(), v.end()) ;//用v的迭代器区间构造pq2

默认构造的是大堆,如果想构造小堆的话,则可以修改优先级队列的第三个参数。


3.2 push函数

push函数其实比较简单,只需要调用底层容器vector的push_back就可以了。

实际上push函数的底层实现是:先在直接调用vector的push_back函数插入,然后再调用向上调整建堆的函数,向上调整这个数,插入完成之后他才能是一个合法的堆。

    priority_queue<int> pq3;
	pq3.push(6);//向优先级队列中插入元素
	pq3.push(3);
	pq3.push(9);
	pq3.push(8);

3.3 pop函数

pop函数比push函数稍微复杂一点,主要就是要先交换第一个值和最后一个值(第一个值即堆顶,最后一个值即堆尾)。然后删除堆尾这个值,再使用向下调整建堆的函数,来使他删除一个值之后还是堆。

pop函数调用:

    pq3.pop();//删除优先级队列第一个元素

向size  empty 以及front函数使用起来就比较简单了,这里就不过多的介绍了。

4.priority_queue的模拟实现

priority_queue的模拟实现主要是对函数的三个模版进行使用和封装。

4.1 仿函数

priority_queue默认是大堆,那么该如何实现小堆呢?需要先了解仿函数

仿函数其实简单理解就是一个重载了operator() 的类对象,通过重载了operator,可以向调用函数一样的调用这个类对象,所以就把他称为仿函数。仿函数的具体细节参考上面链接。

(1) 仿函数的优点

 ① 仿函数比函数指针的执行速度快,函数指针通过地址调用,而仿函数是对运算符operator进行自定义来提高调用的效率。
 ② 仿函数比一般函数灵活,可以同时拥有两个不同的状态实体,一般函数不具备此种功能。
 ③ 仿函数可以作为模板参数使用,因为每个仿函数都拥有自己的类型。

(2)缺点

 ① 需要单独实现一个类。
 ② 定义形式比较复杂。

(3)仿函数的实现

template<class T>
struct Less
{
	bool operator()(const T& l, const T& s)
	{
		return l < s;
	}
};

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

仿函数的使用

int main()
{
	less<int> lessInt;//定义一个仿函数类对象,参数类型指定为int
	std::cout << lessInt(1, 3) << std::endl;//对仿函数的调用等价于std::cout << lessInt.operator()(1, 3) << std::endl;
}

4.2 堆的向上调整和向下调整

1.堆的向上调整

2.堆的向下调整

代码如下:

void AdjustDown(size_t parent)
{
	size_t child = parent * 2 + 1;
	while (child < size())
	{
		if (child + 1 < _con.size() && _con[child] <_con[child + 1] )
			child++;
		if (_con[child] <= _con[parent])
			break;
		else
		{
			swap(_con[child], _con[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
	}
}
void AdjustUp(size_t child)
{
	size_t parent = (child - 1) / 2;
	while (child > 0)
	{
		if (_con[parent] >= _con[child])
			break;
		else
		{
			swap(_con[parent], _con[child]);
			child = parent;
			parent = (child-1)/2;
		}
	}
}

4.3 push和pop函数

void push(const T& val)
{
	_con.push_back(val);
	if(_con.size()>1)
		AdjustUp(_con.size() - 1);
}
void pop()
{
	swap(_con[0], _con[_con.size() - 1]);
	_con.pop_back();
	AdjustDown(0); //将第0个元素进行一次向下调整
}

4.4 总体代码

.h文件

#include <iostream>
#include <vector>
#include <deque>
using namespace std;

namespace njust
{
	template<class T>
	struct Less
	{
		bool operator()(const T& l, const T& s)
		{
			return l < s;
		}
	};

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

	template<class T,class Container=vector<T>,class Cmp=Less<T>>
	class Priority_Queue
	{
	public:
		Priority_Queue()
		{
		}
		template<class InputIterator>
		Priority_Queue(InputIterator first, InputIterator end)
		{
			while (first != end)
			{
				_con.push_back(*first);
				first++;

			}
			//建堆----时间复杂度O(n)
			//默认情况下priority_queue是大堆,故我们这里排升序
			for (size_t i = (_con.size() - 1 - 1) / 2; i >= 0; i--)
			{
				//从第一个非叶子节点开始计算,因为叶子节点天然的就是合法的堆。
				AdjustDown(i);//向下调整算法,建大堆
			}
		}
		void push(const T& val)
		{
			_con.push_back(val);
			if(_con.size()>1)
				AdjustUp(_con.size() - 1);
		}
		void pop()
		{
			swap(_con[0], _con[_con.size() - 1]);
			_con.pop_back();
			AdjustDown(0); //将第0个元素进行一次向下调整
		}
		bool empty()
		{
			return _con.empty();
		}
		T& top()
		{
			return _con[0];
		}
		size_t size()
		{
			return _con.size();
		}
	private:
		//大堆
		void AdjustDown(size_t parent)
		{
			size_t child = parent * 2 + 1;
			while (child < size())
			{
				if (child + 1 < _con.size() && _con[child] <_con[child + 1] )
					child++;
				if (_con[child] <= _con[parent])
					break;
				else
				{
					swap(_con[child], _con[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
			}
		}
		void AdjustUp(size_t child)
		{
			size_t parent = (child - 1) / 2;
			while (child > 0)
			{
				if (_con[parent] >= _con[child])
					break;
				else
				{
					swap(_con[parent], _con[child]);
					child = parent;
					parent = (child-1)/2;
				}
			}
		}
		Container _con;//可以把_con直接理解成为vector
	};
}

.cpp文件

int main()
{
	njust::Priority_Queue<int> pq;
	pq.push(1);
	pq.push(2); 
	pq.push(3); 
	pq.push(4);
	pq.push(5);
	while (!pq.empty())
	{
		cout << pq.top() << " ";
		pq.pop();
	}
	cout << endl;
	return 0;
}

5.本章总结

主要了阐述了优先级队列相关的函数接口如何实现,以及如何自己实现仿函数。笔者认为这一章最重要的就是向上向下调整建堆的过程,以及当使用一个迭代器区间进行初始化的时候,如何进行建堆,从哪个地方开始建。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值