STL之容器适配器priority_queue的实现框架

说明:本文仅供学习交流,转载请标明出处,欢迎转载!

        在前面的文章STL之heap相关操作算法中介绍了堆的相关操作算法,由于堆的注意主要作用是用于排序,我们也知道堆排序的时间复杂度为o(nlogn),是一种不稳定的排序算法,利用堆这一数据结构,我们可以很快第获取一个大数据中最大(或最小)的k个数。同时,上篇文章中,也提出了相关heap算法的一些问题。

        问题1:在调用push_heap函数实现向堆中插入元素之前,我们必须要先将向底层容器的末端插入该元素,然后才能调用push_heap内部的向上调整来将新元素调整到合适的位置。

        问题2:在调用pop_heap函数实现将堆顶元素删除时,我们执行的只是一个伪删除,即并没有将该元素真正从底层容器中删除,只是防止到了容器最后面,而要实现真正的删除,必须在调用pop_heap函数之后,调用底层容器的pop_back操作。

        上面两大问题明显地暴露了heap算法的不足,那就是heap算法的操作并不干净利落,它的很多实际的操作需要显示地调用其底层容器的相关函数来实现。其实,heap算法完全可以干净利落地实现插入和删除的操作,一次性完成,但是收之桑榆则失之东隅,如果将这些操作一次性地包装在heap算法里面,那么就无法实现堆排序,毕竟我们的排序目的是获取一个排好序的数组,而不是一个简单的输出。从这个角度来看,我们不难得出这么一个结论:heap算法主要用于堆排序,我们要引入另外一种容器适配器,来弥补heap算法的不足,那么这个适配器非优先级队列priority_queue莫属。

         priority_queue的底层容器必须是能够提供随机访问迭代器的容器,所以vector和deque容器可以作为priority_queue的底层容器,而list容器则不行。而priority_queue里面的算法都是基对heap算法和容器操作的进一步封装。再正式给出priority_queue的实现框架之前,我想说明下queue和priority_queue的共同点和主要区别

        在前面的文章 STL之容器适配器queue的实现框架中我给出了容器适配器queue的性质和相关操作,现在我们来对比下priority_queue和queque的相同点与区别。

        priority_queue和queue的相同点

        1.都不是容器container,而仅仅是容器适配器container adapter。

        2.都不提供迭代器,也不支持元素的遍历(在不完全弹出元素的前提下)。

        3.都提供empty()、size()、pop()、push(t)四个操作。

        4.都只能在队首弹出元素,不能在尾部弹出元素。

        5.使用这个两个适配器的头文件均为:#include<queue>

        priority_queue和queue的区别

        1.priority_queue是一种优先级队列,只能在队首获取元素,且队首元素的优先级最高(此时并不弹出),而queue仅能在队首获取元素,也能在队尾获取元素。即:priority_queue仅有top()函数,而queue既有front()函数,也有back()函数

        2.priority_queue的基础容器必须能够提供随机访问迭代器,因为priority_queue的算法主要调用的是heap相关算法,而heap相关算法中的迭代器都是随机访问迭代器,所以priority_queue的基础容器只能是vector和deque,不能为list。而queue的基础容器要求必须提供首部删除操作pop_front操作,所以queue的基础容器不能是vector,只能是deque和list

        注意:priority_queue和queue的pop操作内部实现是有区别的。queue的pop操作的内部实现简单的依赖其基础容器的pop_front()函数,而priority_queue的pop操作内部实现由两个操作组成:pop_heap()+底层容器的pop_back(),也就是说,priority_queue的pop操作实际上是先将堆顶的元素与末端元素交换,再对新的堆顶元素执行向下调整操作,最后在调用基础容器的pop_back操作实现最高优先级元素的真正删除。

        下面给出priority_queue的实现代码和测试代码。

实现代码如下:

#include<iostream>
#include<vector>//默认以vector作为底层容器
#include<algorithm>//用到XXX_heap算法
#include<functional>//默认以仿函数less<T>作为优先级比较
using namespace std;
template<class T,class Sequence=vector<T>,class Compare=less<typename Sequence::value_type> >//第三个参数只能是仿函数类,不是对象
class priority_queue
{
	public:
		/*******定义公用访问属性*************/
		typedef typename Sequence::value_type value_type;//定义元素类型
		typedef typename Sequence::size_type size_type;//定义大小类型
		typedef typename Sequence::reference reference;//定义引用类型
		typedef typename Sequence::const_reference const_reference;//定义常引用类型
	protected:
		Sequence c;//底层所采用的顺序容器
		Compare cmp;//底层所采用的仿函数比较方法
	public:
		priority_queue():c(){}//无参构造函数,调用底层容器的默认构造函数
		explicit priority_queue(const Compare& x):c(),cmp(x){}//单形参构造函数,用explicit修饰,防止从形参类型隐式转化为本类类型

		template<class InputIterator>//定义成员模板
		priority_queue(InputIterator first,InputIterator last):c(first,last)//调用底层容器的范围构造函数,采用默认的比较方法
		{
			make_heap(c.begin(),c.end(),cmp);//利用实参范围创建一个堆
		}

		template<class InputIterator>
		prioriry_queue(InputIterator first,InputIterator last,const Compare& x):c(first,last),cmp(x)//调用底层容器的范围构造函数,指定比较方法
		{
			make_heap(c.begin(),c.end(),cmp);//建堆函数
		}

		void push(const value_type& x)//插入操作
		{
			c.push_back(x);//先将待插入的元素放置到原堆的末尾
			push_heap(c.begin(),c.end(),cmp);//再调用向上调整重新维持堆的性质
		}

		void pop()//删除操作
		{
			pop_heap(c.begin(),c.end(),cmp);//先将堆顶元素与最后一个元素交换,对新交换的堆顶元素做向下调整
			c.pop_back();//将元素真正从底层动态数组中删除
		}

		const_reference top()//获取底层容器的第一个元素(即优先级最高的元素)
		{
			return c.front();
		}

		size_type size()const//返回当前优先级队列中元素的个数(等于底层容器的大小)
		{
			return c.size();
		}

		bool empty()//判断优先级队列是否为空
		{
			return c.empty();
		}
};

测试代码如下:

int main()
{
	int arr[]={3,7,2,1,4,9,2,7};
	priority_queue<int,vector<int>,greater<int> >q(arr,arr+sizeof(arr)/sizeof(int));//我们平时用的普通指针必然是一个随机访问迭代器
	cout<<"------------压队列前的元素为----------"<<endl;
	copy(arr,arr+sizeof(arr)/sizeof(int),ostream_iterator<int>(cout," ") );
	cout<<endl;
	//less<T>表示底层是大顶堆,greater<T>表示底层是小顶堆,很好记呀,堆的类型正好与单词意思相反
	/********对初始优先级队列进行操作**********/
	cout<<"------------初始操作----------------"<<endl;
	cout<<"队列的大小:"<<q.size()<<endl;
	cout<<"队首元素:"<<q.top()<<endl;//队手元素的优先级最高了


	/*******top元素出队列以后*************/
	q.pop();
	cout<<"-------------出一次队列后-------------"<<endl;
	cout<<"队列的大小:"<<q.size()<<endl;
	cout<<"队首元素:"<<q.top()<<endl;


	/******向队列中压入元素-3*************/
	q.push(-3);
	cout<<"------------向队列中压入-3之后-----------"<<endl;
	cout<<"队列的大小:"<<q.size()<<endl;
	cout<<"队首元素:"<<q.top()<<endl;


	/*****依次将队列中现有的元素输出******/
	cout<<"-----------依次将队列中现有的元素输出-----"<<endl;
	while(!q.empty())//由于队列不是容器,只是一种容器适配器,不含迭代器,不能提供单一的遍历操作
	{
		cout<<q.top()<<" ";//只能弹出前访问,依次弹出,再访问才能达到访问所有元素的目的
		q.pop();
	}
	cout<<endl;
	return 0;
}

运行结果如下:

STL之容器适配器priority_queue的实现框架_第1张图片

参考文献

[1]《STL源码剖析 侯捷》

[2]《C++primer 第4版》

你可能感兴趣的:(STL之容器适配器priority_queue的实现框架)