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;
}

运行结果如下:

参考文献

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

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

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

时间: 2024-11-08 03:32:33

STL之容器适配器priority_queue的实现框架的相关文章

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

说明:本文仅供学习交流,转载请标明出处,欢迎转载! 上篇文章STL之容器适配器stack的实现框架已经介绍了STL是如何借助基础容器实现一种常用的数据结构stack (栈),本文介绍下另外一种STL内部定义的另外一种STL容器适配器queue(队列). 对于接触过数据结构的人来说,队列并不陌生,它是一种FIFO(first in first out)的数据结构.与栈相比,队列的不同之处在于:(1)队列是一种先进先出的数据结构,而栈则是一种后进先出的数据结构:(2)队列支持首尾两端的访问操作,而栈

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

说明:本文仅供学习交流,转载请标明出处,欢迎转载! 一提到适配器(adapter).我们就想到了早期用电话线上网所用的调制解调器,俗称"猫"."猫"的作用是实现数模转化和模数转化,在client,它能够将电话的模拟信息转化为我们计算机能够接收的数字信息,所以猫相当于一个转换器.再举个更加好理解的样例来说明"适配器"的含义.相信在我们每一个人的家里都有插排,如果就这么一种情况.如今我们家里的墙壁上仅仅有一个三角的插口,而我们的电视却是两个口,怎么办

STL之容器适配器priority_queue

priority_queue(优先队列)是一个拥有权值观念的queue,它允许加入新元素,删除旧元素,审视元素值等功能.由于这是一个queue,所以只允许在底端加入元素,并从顶端取出元素, 除此之外别无其它存取元素的途径. 缺省情况下priority_queue系列利用一个max_heap(最大堆)完成,后者是一个以vector表现的完全二叉树.Max_heap可以满足priority_queue所需要的“依权值高低自动递减排序”的特性.   priority_queue完全以底部容器为根据,再

初探STL之容器适配器

容器适配器 特点 用某种顺序容器来实现(让已有的顺序容器以栈/队列的方式工作) 分类 1) stack: 头文件 <stack> ? 栈 -- 后进先出 2) queue: 头文件 <queue> ? 队列 -- 先进先出 3) priority_queue: 头文件 <queue> ? 优先级队列 -- 最高优先级元素总是第一个出列 注: 容器适配器上没有迭代器 STL中各种排序, 查找, 变序等算法都不适合容器适配器 Stack 特点 1.stack 是后进先出的数

初步STL该容器适配器

容器适配器 特点 容器一定的顺序来实现(让现有的以集装箱堆放/式工作) 分类 1) stack: 头文件 <stack> ? 栈 -- 后进先出 2) queue: 头文件 <queue> ? 队列 -- 先进先出 3) priority_queue: 头文件 <queue> ? 优先级队列 -- 最高优先级元素总是第一个出列 注: 容器适配器上没有迭代器 STL中各种排序, 查找, 变序等算法都不适合容器适配器 Stack 特点 1.stack 是后进先出的数据结构

容器适配器————priority_queue

#include <queue> priority_queue 容器适配器定义了一个元素有序排列的队列.默认队列头部的元素优先级最高.因为它是一个队列,所以只能访问第一个元素,这也意味着优先级最高的元素总是第一个被处理.但是如何定义“优先级”完全取决于我们自己. priority_queue<Type, Container, Functional> Type 就是数据类型, Container 就是容器类型(Container必须是用数组实现的容器,比如vector,deque等等

容器适配器、STL算法简介

可以用某种顺序容器来实现 (让已有的顺序容器以栈/队列的方式工作) 1) stack: 头文件 <stack> 栈 -- 后进先出 2) queue: 头文件 <queue> 队列 -- 先进先出 3) priority_queue: 头文件 <queue> 优先级队列 -- 最高优先级元素总是第一个出列 都有3个成员函数: push: 添加一个元素; top: 返回栈顶部或队头元素的引用 pop: 删除一个元素 容器适配器上没有迭代器 STL中各种排序, 查找, 变序

5.0 容器适配器

STL中容器适配器有stack  queue  priority_queue共三种.他们都是在顺序容器的基础上实现的,屏蔽了顺序容器的一部分功能,突出或增加了另一些功能.容器适配器都有三个成员函数:push ,pop,top. 1)push:添加一个元素 2)top:返回顶部(对stack)或队头(对queue,priority_queue)的元素的引用. 3)pop:删除一个元素. 容器适配器上是没有迭代器的,所以在STL中的各种排序,查找,变序算法都不适用于容器适配器.

STL之stack适配器的实现框架

说明:本文仅供学习交流,转载请标明出处,欢迎转载! 一提到适配器(adapter),我们就想到了早期用电话线上网所用的调制解调器,俗称"猫","猫"的作用是实现数模转化和模数转化,在客户端,它可以将电话的模拟信息转化为我们计算机能够接收的数字信息,所以猫相当于一个转换器.再举个更加好理解的例子来说明"适配器"的含义.相信在我们每个人的家里都有插排,假设就这么一种情况,现在我们家里的墙壁上只有一个三角的插口,而我们的电视却是两个口,怎么办?毫无疑问