您的位置:首页 > 产品设计 > UI/UE

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

2014-06-26 21:17 627 查看
说明:本文仅供学习交流,转载请标明出处,欢迎转载!

在前面的文章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版》
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: