目录
一、queue的简介
1. 队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一端插入元素,另一端提取元素。
2. 队列作为容器适配器实现,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从队尾入队列,从队头出队列。
3. 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类。该底层容器应至少支持以下操作:
empty:检测队列是否为空
size:返回队列中有效元素的个数
front:返回队头元素的引用
back:返回队尾元素的引用
push_back:在队列尾部入队列
pop_front:在队列头部出队列
4. 标准容器类deque和list满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque。
函数声明 | 接口说明 |
构造空的队列 | |
检测队列是否为空,是返回true,否则返回false | |
返回队列中有效元素的个数 | |
返回队头元素的引用 | |
返回队尾元素的引用 | |
在队尾将元素val入队列 |
测试代码
void test_queue()
{
queue<int> q;
q.push(1);
q.push(2);
q.push(3);
q.push(4);
q.push(5);
while(!q.empty())
{
cout<<q.front()<<endl;
q.pop();
}
}
二、queue的模拟实现
与上一篇stack的博文一样,我们此处不再手搓写queue,而是复用已有的代码。同时我们在上一篇博文中已经介绍过适配器的概念,这里我们不再做解释
想要手搓queue可以参考这篇博文
namespace zhuyuan
{
//加入Container模板参数
//我们的STL底层默认是用deque,也就是双端队列实现的。
//支持头插头删,也支持随机访问
//像是vector和list的合集
template<class T,class Container=deque<T>>
class queue
{
public:
void push(const T&x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_front();
}
T& back()
{
//访问尾部的数据.back()
return _con.back();
}
T& front()
{
//访问尾部的数据.back()
return _con.front();
}
const T& back() const
{
//访问尾部的数据.back()
return _con.back();
}
const T& front() const
{
//访问尾部的数据.back()
return _con.front();
}
bool empty() const
{
return _con.empty();
}
size_t size() const
{
return _con.size();
}
private:
//封装一个vector
// vector<T> _con;
//任何的类型模板,主要满足上面的那些条件(push等等功能),就可以变成container
Container _con;
};
}
#endif //STACK_QUEUETEST_QUEUE_H
三、priority_queue的简介
1. 优先队列是一种容器适配器,根据严格的弱排序标准,它的第一个元素总是它所包含的元素中最大的。
2. 此上下文类似于堆,在堆中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。
3. 优先队列被实现为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定的成员函数来访问其元素。元素从特定容器的“尾部”弹出,其称为优先队列的顶部。
4. 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类。容器应该可以通过随机访问迭代器访问,并支持以下操作:
empty():检测容器是否为空
size():返回容器中有效元素个数
front():返回容器中第一个元素的引用
push_back():在容器尾部插入元素
pop_back():删除容器尾部元素
5. 标准容器类vector和deque满足这些需求。默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。
6. 需要支持随机访问迭代器,以便始终在内部保持堆结构。容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作。
函数声明 | 接口说明 |
构造一个空的优先级队列 | |
检测优先级队列是否为空,是返回true,否则返回 false | |
返回优先级队列中最大(最小元素),即堆顶元素 | |
在优先级队列中插入元素x | |
pop() | 删除优先级队列中最大(最小)元素,即堆顶元素 |
也就是说priority_queue其实就是一个堆
测试代码
void test_priority_queue()
{
//默认大的优先级高
priority_queue<int> pq;
pq.push(3);
pq.push(1);
pq.push(2);
pq.push(5);
pq.push(7);
pq.push(0);
pq.push(3);
//不支持迭代器遍历,这里我们让其逐个出堆,得到顺序
while(!pq.empty())
{
cout<<pq.top()<<" ";
pq.pop();
}
cout<<endl;
//用一段区间去初始化
int a[]={3,2,7,6,0,4,1,9,8,5};
//设置greater让其变成一个小根堆
//设置less让其变成一个大根堆
priority_queue<int,vector<int>,greater<int>> heap(a,a+sizeof (a)/sizeof(int));
while(!heap.empty())
{
cout<<heap.top()<<" ";
heap.pop();
}
cout<<endl;
}
可以知道默认是一个大根堆,也就是说参数是less
四、仿函数
就像上面的测试代码中的一样,如果我们想要实现一个大根堆,就在构造的时候传入参数less<T>,如果我们想要构建一个小根堆的话,就在构造的时候传入参数greater<T>,我们如何才能模拟实现这个功能呢?
在下面的代码中我们就创建了两个仿函数分别为less和greater(其实就是两个类)
less在l<r的时候返回1,greater在l>r的时候返回1
其中我们都是用operator()()来重载的。
仿函数的目的就是让类对象能够像函数一样去被使用!
//仿函数/函数对象 —类,重载operator()
//类对象可以像函数一样去使用
namespace zhuyuan
{
template<class T>
class less{
public:
bool operator()(const T&l,const int & r) const
{
return l<r;
}
};
template<class T>
class greater{
public:
bool operator()(const T&l,const int & r) const
{
return l>r;
}
};
}
测试代码
int main() {
zhuyuan::less<int> lsFunc;
cout<<lsFunc(1,2)<<endl;
// 等价于下面
cout<<lsFunc.operator()(1,2)<<endl;
zhuyuan::greater<int> gtFunc;
cout<<gtFunc(1,2)<<endl;
// 等价于下面
cout<<gtFunc.operator()(1,2)<<endl;
return 0;
}
五、priority_queue的模拟实现
与堆相关的知识还可以参考
#ifndef STACK_QUEUETEST_PRIORITYQUEUE_H
#define STACK_QUEUETEST_PRIORITYQUEUE_H
#pragma once
namespace zhuyuan
{
//大堆
//ComPare是一个进行比较的仿函数 less ->大堆
//ComPare是一个进行比较的仿函数 greater ->小堆
template<class T,class Container=vector<T>,class ComPare=std::less<T>>
class priority_queue
{
public:
priority_queue()
{
}
//使用迭代器进行范围拷贝构造
template <class InputIterator>
priority_queue(InputIterator first ,InputIterator last)
{
while(first!=last)
{
_con.push_back(*first);
++first;
}
//第一个-1因为数组下表从0开始,再-1然后/2是为了找到叶子结点的上面那一层的最后一个结点
for(int i=(_con.size()-1-1)/2;i>=0;--i)
{
//挨个向下调整
adjust_down(i);
}
}
void adjust_up(size_t child)
{
ComPare com;
size_t parent=(child-1)/2;
//当孩子结点还没有被换到根节点的时候不断进行循环
//logN
while(child>0)
{
//由于我们默认的仿函数是less,所以我们需要调整为<,也就是小于号
// if(_con[child]>_con[parent])
// if(_con[parent]<_con[child])
if(com(_con[parent],_con[child]))
{
std:swap(_con[child],_con[parent]);
child=parent;
parent=(child-1)/2;
}
else
{
break;
}
}
}
void push(const T& x)
{
_con.push_back(x);
//向上调整
adjust_up(_con.size()-1);
}
//最多向下调整logN次
void adjust_down(size_t parent)
{
//创建仿函数
ComPare com;
size_t child=parent*2 +1;
while(child<_con.size())
{
//选出左右孩子中大的那一个
// if(child+1<_con.size()&&_con[child+1]>_con[child])
// if(child+1<_con.size()&&_con[child]<_con[child+1])
if(child+1<_con.size()&&com(_con[child],_con[child+1]))
{
++child;
}
// if(_con[child]>_con[parent])
// if(_con[parent]<_con[child])
if(com(_con[parent],_con[child]))
{
std:swap(_con[child],_con[parent]);
parent=child;
child=parent*2+1;
}
else
{
break;
}
}
}
//;支持删除堆顶的数据
void pop()
{
std::swap(_con[0],_con[_con.size()-1]);
_con.pop_back();
//从根开始调整
adjust_down(0);
}
//取出栈顶元素
const T& top()
{
return _con[0];
}
//判断是否为空
bool empty() const
{
return _con.empty();
}
//判断大小
size_t size() const
{
return _con.size();
}
private:
Container _con;
};
}
#endif //STACK_QUEUETEST_PRIORITYQUEUE_H
测试代码
void test_mypriority_queue()
{
//默认大的优先级高
// std::priority_queue<int> pq(std::less<int>());
zhuyuan:priority_queue<int> pq;
pq.push(3);
pq.push(1);
pq.push(2);
pq.push(5);
pq.push(7);
pq.push(0);
pq.push(3);
//不支持迭代器遍历
while(!pq.empty())
{
cout<<pq.top()<<" ";
pq.pop();
}
cout<<endl;
//用一段区间去初始化
int a[]={3,2,7,6,0,4,1,9,8,5};
//设置greater让其变成一个小堆
zhuyuan::priority_queue<int,vector<int>,greater<int>> heap(a,a+sizeof (a)/sizeof(int));
while(!heap.empty())
{
cout<<heap.top()<<" ";
heap.pop();
}
cout<<endl;
}