STL之heap与优先级队列Priority Queue详解
一、heap
heap并不属于STL容器组件,它分为 max heap 和min heap,在缺省情况下,max-heap是优先队列(priority queue)的底层实现机制。而这个实现机制中的max-heap实际上
是以一个vector表现的完全二叉树(complete binary tree)。STL在<algorithm.h>中实现了对 存储在vector/deque 中的元素进行堆操作的函数,包括make_heap, pop_heap, push_heap, sort_heap,对不愿自己写数据结构堆的C++选手来说,这几个算法函数很有用,详细解释可以参见: http://www.cplusplus.com/reference/algorithm/make_heap/
下面的_First与_Last为可以随机访问的迭代器(指针),_Comp为比较函数(仿函数),其规则——如果函数的第一个参数小于第二个参数应返回true,否则返回false。
1.make_heap():
make_heap(_First, _Last)
make_heap(_First, _Last, _Comp)
默认是建立最大堆的。对int类型,可以在第三个参数传入greater<int>()得到最小堆。
2.push_heap(_First, _Last):
新添加一个元素在末尾,然后重新调整堆序。也就是把元素添加在底层vector的end()处。
该算法必须是在一个已经满足堆序的条件下,添加元素。该函数接受两个随机迭代器,分别表示first,end,区间范围。
关键是我们执行一个siftup()函数,上溯函数来重新调整堆序。具体的函数机理很简单,可以参考我的编程珠玑里面堆的实现的文章。
3.pop_heap(_First, _Last):
这个算法跟push_heap类似,参数一样。不同的是我们把堆顶元素取出来,放到了数组或者是vector的末尾,用原来末尾元素去替代,然后end迭代器减1,执行siftdown()下溯函数来重新调整堆序。
注意算法执行完毕后,最大的元素并没有被取走,而是放于底层容器的末尾。如果要取走,则可以使用底部容器(vector)提供的pop_back()函数。
4.sort_heap(_First, _Last):
既然每次pop_heap可以获得堆中最大的元素,那么我们持续对整个heap做pop_heap操作,每次将操作的范围向前缩减一个元素。当整个程序执行完毕后,我们得到一个非降的序列。注意这个排序执行的前提是,在一个堆上执行。
下面是这几个函数操作vector中元素的例子。
#include<iostream>
#include<vector>
#include<algorithm> using namespace std; int main()
{
int a[] = {15, 1, 12, 30, 20};
vector<int> ivec(a, a+5);
for(vector<int>::iterator iter=ivec.begin();iter!=ivec.end();++iter)
cout<<*iter<<" ";
cout<<endl; make_heap(ivec.begin(), ivec.end());//建堆
for(vector<int>::iterator iter=ivec.begin();iter!=ivec.end();++iter)
cout<<*iter<<" ";
cout<<endl; pop_heap(ivec.begin(), ivec.end());//先pop,然后在容器中删除
ivec.pop_back();
for(vector<int>::iterator iter=ivec.begin();iter!=ivec.end();++iter)
cout<<*iter<<" ";
cout<<endl; ivec.push_back(99);//先在容器中加入,再push
push_heap(ivec.begin(), ivec.end());
for(vector<int>::iterator iter=ivec.begin();iter!=ivec.end();++iter)
cout<<*iter<<" ";
cout<<endl; sort_heap(ivec.begin(), ivec.end());
for(vector<int>::iterator iter=ivec.begin();iter!=ivec.end();++iter)
cout<<*iter<<" ";
cout<<endl; return 0;
}
二、priority queue
优先队列(priority_queue)首先是一个queue,那就是必须在末端推入,必须在顶端取出元素。除此之外别无其他存取元素的途径。内部元素按优先级高低排序,优先级高的在前。缺省情况下,priority_heap利用一个max-heap完成,后者是一个以vector表现的完全二叉树。我们说优先队列不是一个STL容器,它以底部容器而实现,修改了接口,形成另一种性质,这样的东西称之为适配器(adapter)。
详情参见: http://www.cplusplus.com/reference/stl/priority_queue/
优先级队列是一个拥有权值观念的queue。它允许在底端添加元素、在顶端去除元素、删除元素。
优先级队列内部的元素并不是按照添加的顺序排列,而是自动依照元素的权值排列。权值最高者排在最前面。
缺省情况下,优先级队列利用一个大顶堆完成。关于堆可以参考:STL堆详解与编程实现
优先级队列以底部容器完成其所有工作,具有这种“修改某物接口,形成另一种风貌”这种性质者,成为配接器(adapter)。在STL中优先级队列不被归类为容器,而被归类为容器配接器(container
adapter)
priority_queue 对于基本类型的使用方法相对简单。
他的模板声明带有三个参数,priority_queue<Type, Container, Functional>
Type 为数据类型, Container 为保存数据的容器,Functional 为元素比较方式。
Container 必须是用数组实现的容器,比如 vector, deque 但不能用 list.
STL里面容器默认用的是 vector. 比较方式默认用 operator< , 所以如果你把后面俩个参数 缺省的话,优先队列就是大顶堆,队头元素最大。
看例子
#include <iostream>
#include <queue>
using namespace std;
int main(){
priority_queue<int,vector<int>,less<int> >q;//使用priority_queue<int> q1;一样
for(int i=0;i<10;i++)
q1.push(i);
while(!q1.empty()){
cout<<q1.top()<< endl;
q1.pop();
}
return 0;
}
如果要用到小顶堆,则一般要把模板的三个参数都带进去。
STL里面定义了一个仿函数 greater<>,对于基本类型可以用这个仿函数声明小顶堆
例子:
#include <iostream>
#include <queue>
using namespace std;
int main(){
priority_queue<int,vector<int>,greater<int> >q;
for(int i=0;i<10;i++)
q.push(i);
while(!q.empty()){
cout<<q.top()<< endl;
q.pop();
}
return 0;
}
对于自定义类型,则必须自己重载 operator< 或者自己写仿函数先看看例子:
#include <iostream>
#include <queue>
using namespace std;
struct Node{
int x, y;
}node;
bool operator<( Node a, Node b){
if(a.x==b.x) return a.y>b.y;
return a.x>b.x;
}
int main(){
priority_queue<Node>q;
for(int i=0;i<10;i++){
node.x=i;
node.y=10-i/2;
q.push(node);
}
while(!q.empty()){
cout<<q.top().x <<' '<<q.top().y<<endl;
q.pop();
}
return 0;
}
自定义类型重载 operator< 后,声明对象时就可以只带一个模板参数。
此时不能像基本类型这样声明priority_queue<Node, vector<Node>, greater<Node> >;
原因是 greater<Node> 没有定义,如果想用这种方法定义
则可以按如下方式例子:(个人喜欢这种方法,因为set的自定义比较函数也可以写成这种形式)
#include <iostream>
#include <queue>
using namespace std;
struct Node{
int x, y;
}node;
struct cmp{
bool operator()(Node a,Node b){
if(a.x==b.x) return a.y>b.y;
return a.x>b.x;}
}; int main(){
priority_queue<Node,vector<Node>,cmp>q;
for(int i=0;i<10;i++){
node.x=i;
node.y=10-i/2;
q.push(node);
}
while(!q.empty()){
cout<<q.top().x<<' '<<q.top().y<<endl;
q.pop();
}
return 0;
}
SGI STL中优先级队列定义
定义完整代码:
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_referenceconst_reference;
protected:
Sequence c; //底层容器
Compare comp;//元素大小比较标准
public:
priority_queue() : c() {}
explicit priority_queue(const Compare& x) : c(), comp(x) {} //以下用到的 make_heap(), push_heap(), pop_heap()都是泛型算法
//注意,任一个建构式都立刻于底层容器内产生一个 implicit representation heap。
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last, const Compare& x)
: c(first, last), comp(x) {make_heap(c.begin(), c.end(), comp); }
template <class InputIterator>
priority_queue(InputIterator first, InputIterator last)
: c(first, last) { make_heap(c.begin(), c.end(), comp); } bool empty() const { return c.empty(); }
size_typesize() const { return c.size(); }
const_referencetop() const { return c.front(); }
void push(const value_type& x) {
__STL_TRY {
// push_heap是泛型算法,先利用底层容器的 push_back() 将新元素
// 推入端,再重排 heap。见 C++ Primer p.1195。
c.push_back(x);
push_heap(c.begin(), c.end(), comp);// push_heap是泛型算法
}
__STL_UNWIND(c.clear());
}
void pop() {
__STL_TRY {
// pop_heap 是泛型算法,从 heap 内取出一个元素。它并不是真正将元素
// 弹出,而是重排 heap,然后再以底层容器的 pop_back() 取得被弹出
// 的元素。见 C++ Primer p.1195。
pop_heap(c.begin(), c.end(), comp);
c.pop_back();
}
__STL_UNWIND(c.clear());
}
};
优先级队列编程实现(C Plus Plus)
在这里自己用C++编程实现了简易版的优先级队列。
其中用到了前一篇博客里面的堆heap.h:
//STL堆算法实现(大顶堆)
//包含容器vector的头文件:Heap用vector来存储元素
#include <vector>
#include <iostream>
#include <functional>
#define MAX_VALUE 999999 //某个很大的值,存放在vector的第一个位置(最大堆)
const int StartIndex = 1;//容器中堆元素起始索引
using namespace std;
//堆类定义
//默认比较规则less
template <class ElemType,class Compare = less<ElemType> >
class MyHeap{
private:
vector<ElemType> heapDataVec;//存放元素的容器
int numCounts;//堆中元素个数
Compare comp;//比较规则
public:
MyHeap();
vector<ElemType>& getVec();
void initHeap(ElemType *data,const int n);//初始化操作
void printfHeap();//输出堆元素
void makeHeap();//建堆
void sortHeap();//堆排序算法
void pushHeap(ElemType elem);//向堆中插入元素
void popHeap();//从堆中取出堆顶的元素
void adjustHeap(int childTree,ElemType adjustValue);//调整子树
void percolateUp(int holeIndex,ElemType adjustValue);//上溯操作
void setNumCounts(int val);//设置当前所要构建的堆中元素个数
};
template <class ElemType,class Compare>
MyHeap<ElemType,Compare>::MyHeap()
:numCounts(0)
{
heapDataVec.push_back(MAX_VALUE);
}
template <class ElemType,class Compare>
vector<ElemType>& MyHeap<ElemType,Compare>::getVec()
{
return heapDataVec;
}
template <class ElemType,class Compare>
void MyHeap<ElemType,Compare>::initHeap(ElemType *data,const int n)
{
//拷贝元素数据到vector中
for (int i = 0;i < n;++i)
{
heapDataVec.push_back(*(data + i));
++numCounts;
}
}
template <class ElemType,class Compare>
void MyHeap<ElemType,Compare>::printfHeap()
{
cout << "Heap : ";
for (int i = 1;i <= numCounts;++i)
{
cout << heapDataVec[i] << " ";
}
cout << endl;
}
template <class ElemType,class Compare>
void MyHeap<ElemType,Compare>::makeHeap()
{
//建堆的过程就是一个不断调整堆的过程,循环调用函数adjustHeap依次调整子树
if (numCounts < 2)
return;
//第一个需要调整的子树的根节点多音
int parent = numCounts / 2;
while(1)
{
adjustHeap(parent,heapDataVec[parent]);
if (StartIndex == parent)//到达根节点
return;
--parent;
}
}
template <class ElemType,class Compare>
void MyHeap<ElemType,Compare>::sortHeap()
{
//堆排序思路
//每执行一次popHeap操作,堆顶的元素被放置在尾端,然后针对前面的一次再执行popHeap操作
//依次下去,最后即得到排序结果
while(numCounts > 0)
popHeap();
}
template <class ElemType,class Compare>
void MyHeap<ElemType,Compare>::pushHeap(ElemType elem)
{
//将新元素添加到vector中
heapDataVec.push_back(elem);
++numCounts;
//执行一次上溯操作,调整堆,以使其满足最大堆的性质
percolateUp(numCounts,heapDataVec[numCounts]);
}
template <class ElemType,class Compare>
void MyHeap<ElemType,Compare>::popHeap()
{
//将堆顶的元素放在容器的最尾部,然后将尾部的原元素作为调整值,重新生成堆
ElemType adjustValue = heapDataVec[numCounts];
//堆顶元素为容器的首元素
heapDataVec[numCounts] = heapDataVec[StartIndex];
//堆中元素数目减一
--numCounts;
adjustHeap(StartIndex,adjustValue);
}
//调整以childTree为根的子树为堆
template <class ElemType,class Compare>
void MyHeap<ElemType,Compare>::adjustHeap(int childTree,ElemType adjustValue)
{
//洞节点索引
int holeIndex = childTree;
int secondChid = 2 * holeIndex + 1;//洞节点的右子节点(注意:起始索引从1开始)
while(secondChid <= numCounts)
{
if (comp(heapDataVec[secondChid],heapDataVec[secondChid - 1]))
{
--secondChid;//表示两个子节点中值较大的那个
}
//上溯
heapDataVec[holeIndex] = heapDataVec[secondChid];//令较大值为洞值
holeIndex = secondChid;//洞节点索引下移
secondChid = 2 * secondChid + 1;//重新计算洞节点右子节点
}
//如果洞节点只有左子节点
if (secondChid == numCounts + 1)
{
//令左子节点值为洞值
heapDataVec[holeIndex] = heapDataVec[secondChid - 1];
holeIndex = secondChid - 1;
}
//将调整值赋予洞节点
heapDataVec[holeIndex] = adjustValue;
//此时可能尚未满足堆的特性,需要再执行一次上溯操作
percolateUp(holeIndex,adjustValue);
}
//上溯操作
template <class ElemType,class Compare>
void MyHeap<ElemType,Compare>::percolateUp(int holeIndex,ElemType adjustValue)
{
//将新节点与其父节点进行比较,如果键值比其父节点大,就父子交换位置。
//如此,知道不需要对换或直到根节点为止
int parentIndex = holeIndex / 2;
while(holeIndex > StartIndex && comp(heapDataVec[parentIndex],adjustValue))
{
heapDataVec[holeIndex] = heapDataVec[parentIndex];
holeIndex = parentIndex;
parentIndex /= 2;
}
heapDataVec[holeIndex] = adjustValue;//将新值放置在正确的位置
}
template <class ElemType,class Compare>
void MyHeap<ElemType,Compare>::setNumCounts(int val)
{
numCounts = val;
}
PriorityQueue.h:
#include "Heap.h"
//优先级队列类定义
//默认:值最小的权值最大
template <class ElemType,class Compare = less<ElemType> >
class MyPriorityQueue{
private:
MyHeap<ElemType,Compare> heap;//底层用堆实现
public:
//构造函数
MyPriorityQueue(ElemType *data,int n);
//判断优先级队列是否为空
int empty(){return heap.getVec().size() - 1;}
//返回优先级队列大小
long size(){return heap.getVec().size() - 1;}//注意底层容器第一个元素是无效元素
//取得优先级队列头元素
ElemType top(){return heap.getVec()[StartIndex];}
//添加元素
void push(const ElemType &val);
//弹出队首元素
void pop();
MyHeap<ElemType,Compare>& getHeap(){return heap;};
};
template <class ElemType,class Compare>
MyPriorityQueue<ElemType,Compare>::MyPriorityQueue(ElemType *data, int n)
{
heap.initHeap(data,n);
heap.makeHeap();
heap.sortHeap();
}
template <class ElemType,class Compare>
void MyPriorityQueue<ElemType,Compare>::push(const ElemType &val)
{
heap.setNumCounts(heap.getVec().size() - 1);//排除容器首部的哨兵元素
heap.makeHeap();
heap.pushHeap(val);
heap.sortHeap();
}
template <class ElemType,class Compare>
void MyPriorityQueue<ElemType,Compare>::pop()
{
heap.getVec().erase(heap.getVec().begin() + 1);//删除队列首部的元素
heap.setNumCounts(heap.getVec().size() - 1);//排除容器首部的哨兵元素
heap.makeHeap();
heap.sortHeap();
}
PriorityQueueTest.cpp:
#include "PriorityQueue.h"
#include <iostream>
#include <string>
using namespace std;
int main()
{
const int n = 9;
int data[n] = {0,1,2,3,4,8,9,3,5};
MyPriorityQueue<int> *priorityObj1 = new MyPriorityQueue<int>(data,n);
cout << "Current Heap: " << endl;
for (int i = 1;i <= priorityObj1->size();++i)
{
cout << priorityObj1->getHeap().getVec()[i] << " ";
}
cout << endl;
cout << "Size = " << priorityObj1->size() << endl;
cout << "Top element = " << priorityObj1->top() << endl;
priorityObj1->pop();
cout << "After pop one element:" << endl;
cout << "Size = " << priorityObj1->size() << endl;
cout << "Top element = " << priorityObj1->top() << endl;
cout << "Current Heap: " << endl;
for (int i = 1;i <= priorityObj1->size();++i)
{
cout << priorityObj1->getHeap().getVec()[i] << " ";
}
cout << endl;
priorityObj1->pop();
cout << "After pop one element:" << endl;
cout << "Size = " << priorityObj1->size() << endl;
cout << "Top element = " << priorityObj1->top() << endl;
cout << "Current Heap: " << endl;
for (int i = 1;i <= priorityObj1->size();++i)
{
cout << priorityObj1->getHeap().getVec()[i] << " ";
}
cout << endl;
priorityObj1->push(7);
cout << "After push one element 7:" << endl;
cout << "Size = " << priorityObj1->size() << endl;
cout << "Top element = " << priorityObj1->top() << endl;
cout << "Current Heap: " << endl;
for (int i = 1;i <= priorityObj1->size();++i)
{
cout << priorityObj1->getHeap().getVec()[i] << " ";
}
cout << endl;
delete priorityObj1;
}
运算结果:

STL之heap与优先级队列Priority Queue详解的更多相关文章
- 算法与数据结构基础 - 堆(Heap)和优先级队列(Priority queue)
堆基础 堆(Heap)是具有这样性质的数据结构:1/完全二叉树 2/所有节点的值大于等于(或小于等于)子节点的值: 图片来源:这里 堆可以用数组存储,插入.删除会触发节点shift_down.shif ...
- jQuery 源码分析(十一) 队列模块 Queue详解
队列是常用的数据结构之一,只允许在表的前端(队头)进行删除操作(出队),在表的后端(队尾)进行插入操作(入队).特点是先进先出,最先插入的元素最先被删除. 在jQuery内部,队列模块为动画模块提供基 ...
- 什么是Java优先级队列(Priority Queue)?
PriorityQueue是一个基于优先级堆的无界队列.它的元素是按照自然顺序排序的.在创建元素的时候,我们给它一个一个负责排序的比较器.PriorityQueue不允许null值,因为 它们没有自然 ...
- GO语言heap剖析及利用heap实现优先级队列
GO语言heap剖析 本节内容 heap使用 heap提供的方法 heap源码剖析 利用heap实现优先级队列 1. heap使用 在go语言的标准库container中,实现了三中数据类型:heap ...
- Python线程优先级队列(Queue)
Python的Queue模块中提供了同步的.线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列 LifoQueue,和优先级队列PriorityQueue.这些队列都实 ...
- python多线程--优先级队列(Queue)
Python的Queue模块中提供了同步的.线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue.这些队列都实现 ...
- 线程优先级队列( Queue)
Python的Queue模块中提供了同步的.线程安全的队列类,包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue.这些队列都实现 ...
- STL测试3)优先级队列实现二叉堆
用法: big_heap.empty();判断堆是否为空 big_heap.pop();弹出栈顶元素最大值 big_heap.push(x);将x添加到最大堆 big_heap.top();返回栈顶元 ...
- 2.6 C++STL queue详解
文章目录 2.6.1 引入 2.6.2 代码示例 2.6.3 代码运行结果 总结 2.6.1 引入 首先,在STL中 queue 和 stack 其实并不叫容器(container),而是叫适配器(a ...
随机推荐
- emmc基础技术8:操作模式4-data transfer mode
1.前言 eMMC总线操作包含: boot mode, device identification mode interrupt mode data transfer mode 本文主要描述data ...
- Oracle11g的database 和client的区别是什么?
由于工作需要,刚开始接触oracle数据库,完全小白,下载的时候看到有database和client两种类型可供下载,一时不知如何是好,于是网上询问得知其中区别,在此记录一下自己的无知. “datab ...
- Jenkins实现定时、顺序编译
1 Jenkins实现定时.顺序编译 l Jenkins 编译流程:更新代码,编译公共服务,编译普通服务(普通服务依赖于公共服务).以下图为例,首先执行 update,再执行 icto_c ...
- 转载:编译安装Nginx(1.4)《深入理解Nginx》(陶辉)
原文:https://book.2cto.com/201304/19617.html 安装Nginx最简单的方式是,进入nginx-1.0.14目录后执行以下3行命令:./configuremakem ...
- spring boot 中的热部署
<plugin> <groupId>org.springframework.boot</groupId> <artifactId>sprin ...
- python语法小应用---列表和元组
声明:本文章为参考总结CSDN上知识点所获,只是用来总结自己学习而用,如有侵权,会删除! 列表(list): 列表就像一个线性容器,但是比C++的 lis t扩展多得多 列表里的元素可以是相同类型,也 ...
- python魔法函数(二)之__getitem__、__len__、__iter__
魔法函数会增强python类的类型,独立存在 __getitem class Company: def __init__(self, employees): self.employees = empl ...
- [工具/PC]计算机中丢失libiconv-2.dll,丢失libintl-8.dll,无法定位程序输入点libiconv于动态链接库libiconv-2.dll上问题解决方法
CodeBlocks 1. 背景,为了学习C语言,在win系统上下载了codeBlock,先简单介绍下:Code::Blocks 是一个开放源码的全功能的跨平台C/C++集成开发环境. Code::B ...
- 分享我对JS插件开发的一些感想和心得
本文阅读目录: •起因•如何开发一个轻量级的适用性强的插件•总结 起因 如果大家平时做过一些前端开发方面的工作,一定会有这样的体会:页面需要某种效果或者插件的时候,我们一般会有两种选择: 1.上网查找 ...
- Spark的HA部署
一.安装JDK.Scala 二.安装zookeeper 三.安装Hadoop 四.安装Spark 1.修改spark/conf/spark-env.sh export JAVA_HOME=/usr/j ...