目录
2.4.为什么选择deque作为stack和queue的底层默认容器
1.引言
这篇讲stack和queue的使用和模拟实现,在学这俩个容器前,需要掌握这些内容
虽然我们称stack,queue,priority_queue为容器,但更准确的说应该是容器适配器

具体什么是适配器也会在这篇文章中讲到,那么话不多说直接进入正文------------------>

2.容器适配器(重点)
2.1.什么是适配器
适配器是一种设计模式 (设计模式是一套被反复使用的,多数人知晓的,经过分类编目的,代码设计经验的总和),这种模式是将一个类的接口转换成客户希望的另外一个接口
就比如我们的充电器,又可以叫做电源适配器,因为可以对出来的电进行转换
2.2.STL标准库中stack和queue的底层结构
虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器适配器 ,这是因为stack和队列只是对其他容器的接口进行了包装,STL中stack和queue默认使用deque,比如:



这个Container就是容器的意思,这个模板参数就是意味着用什么容器构建栈
2.3.deque的简单介绍
deque虽然叫双端队列,但其实deque跟queue没有关系,队列要求先进先出,deque不要求先进先出
我们可以把deque当成vector和list的缝合怪
2.3.1.deque的原理介绍
deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高。(这也是为什么产生deque的原因,vector和list的优缺点过于明显,我这里汇总一下)

我们看图,可知deque用的是随机迭代器

我们从这就可以看出deque是个缝合怪,既支持下标访问,又支持头插头删

在讲deque底层结构前,我们先回顾下vector和list的结构,vector是一段连续的空间,list是一个一个节点作为空间,也就是很多小空间,然后通过指针连接在一起

deque为了解决vector频繁扩容的问题,采取的操作是开辟一块连续空间,如果连续空间满了,就开辟下一块同样大小的空间,不扩容,直接开辟新空间,也就是这样

这些空间连接deque是运用中控数组来实现,就是deque有一个中控数组,中控数组里面存的是指针,指向的就是对应空间,然后一开始的空间都会尽量往中控数组的中间位置靠,如下图

这样的话想要头插,尾插就会很方便,如下图

当然,这个也会要扩容,只是不会像vector一样拷贝的那么多,如果中控数组满了,就会扩容出一个更大的中控数组,然后把指针拷贝过去
那么假设一块空间buf的大小是N,那么如果想要获取第i个位置的数据怎么算呢,这个其实很简单,我们通过 i/N就能知道这个i是在第几个buf数组,然后再i%N就能知道这个i是在那个buf数组的第几个位置。那我们就可以通过中控数组先找到对应的buf数组,然后再进里面找对应位置的元素
双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其"整体连续"以及随机访问的假象,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂,如下图所示:
deque的迭代器里面有四个成员,first,last指向buf空间的开始和结束,cur指向的是想要访问的数据,node指向的是中控数组当前指向该buf空间的指针的位置,用图表示的话就是如下图

如果理解了迭代器,就理解了这个结构,从实现上来考量的话,重点要考量这个结构

这里我简单介绍下,deque主要的成员变量就俩个,start其实就是它的begin()返回的接待起,finish其实就是它的end()返回的迭代器
我们想要遍历依旧如此
cpp
iterator it = begin();
while(it!=end())
{
cout<<*it<<" ";
++it;
}
这边的it!=end()其实就是it.cur!=finish.cur。然后*it其实就是*(it.cur),然后++it就是这里迭代器最关键的地方,它内部先判断it.cur是否it.last,如果不相等,直接++it.cur就可以了,如果相等,那就说明这块空间遍历完了,此时就通过node找到对应的中控数组的位置,随后让it.node++,这个时候就找到了指向下一块空间的指针,然后更新cur,first和last即可
接下来我再来讲下deque的头插和尾插
尾插很简单,就看finish.cur是不是 等于finish.last,如果不等于,就相当于没满,直接插入随后cur++就可以了,如果满了,就开辟新空间,更新finish迭代器,随后插入即可
头插其实也简单,就看start.cur是不是等于start.first,因为是头插,所以开辟的空间其实是从后往前插入的,如果不等于相当于没满,直接插入随后cur--即可,如果满了就开辟新空间,更新start迭代器,随后插入即可(从后往前)
头删尾删也是同理,就不细讲了
讲了头插尾插后,其实上面我们所讲的访问第i个位置数据的方式也就要改进了,因为我们第一个buf的空间不能保证是满的,所以我们要先判断在不在第一个buf空间,如果不在,减去第一个buf的数据个数,再用/和%来找到对应位置
2.3.2.deque的优缺点
deque的头插尾插效率很高,甚至比vector和list更胜一筹,首先vector会有扩容方面问题,是这三个里效率最低的毋庸置疑,list和deque相比,deque每次开空间是一段,而list是一个一个开空间,前者的效率肯定是更高的,因为频繁申请空间效率肯定是比单次申请空间效率低的,deque虽然还多了个中控数组扩容,但是那个的代价非常低,所以不影响
inset和erase效率极低,甚至低于vector和list,因为deque和vector在增删的行为模式上很相似,但是他与vector相比还有判断是否跨buf
总结:
- deque头插尾插效率很高,更甚于vector和list
- 下标随机访问效率也不错,但是比不上vector
- inset和erase效率极低,甚至低于vector和list
致命缺陷 :不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多
2.4.为什么选择deque作为stack和queue的底层默认容器
我们了解deque后,可以知道,deque在头插头删尾插尾删方面的效率是极高的,而stack和queue都不需要访问中间的元素,只会对头部或者尾部的元素进行查找插入删除,这个时候,deque就非常适配了
3.stack的介绍和使用
3.1.stack介绍
stack就是栈,我们直接看图

stack的模板参数列表里就没有allocator了
3.2.stack的使用
我们接下来来看各个接口,可以发现其实和前面那些容器都差不多,基本上一眼懂

emplace效果和push是一样的,这里最主要的三个接口就是top(返回栈顶元素),push(入栈),pop(出栈)
要用需要加上#include <stack>头文件
这里直接用题来训练
AC码
cpp
#include <stack>
class MinStack {
public:
MinStack() {
}
void push(int value) {
st.push(value);
if(stm.empty()||value<=stm.top())
stm.push(value);
}
void pop() {
if(st.top()==stm.top())
stm.pop();
st.pop();
}
int top() {
return st.top();
}
int getMin() {
return stm.top();
}
stack<int> st;
stack<int> stm;
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(value);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->getMin();
*/
AC码
cpp
#include <stack>
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pushV int整型vector
* @param popV int整型vector
* @return bool布尔型
*/
bool IsPopOrder(vector<int>& pushV, vector<int>& popV) {
stack<int> sti;
stack<int> sto;
for (int i = popV.size() - 1; i >= 0; i--)
sto.push(popV[i]);
for (size_t i = 0; i < pushV.size(); i++) {
if (pushV[i] == sto.top()) {
sto.pop();
while (!sti.empty() && sti.top() == sto.top()) {
sti.pop();
sto.pop();
}
} else
sti.push(pushV[i]);
}
while (!sti.empty() && sti.top() == sto.top()) {
sti.pop();
sto.pop();
}
return sti.empty();
}
};
AC码
cpp
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
int x = 0;
for(int i = 0;i<tokens.size();i++)
{
int x1,y1;
if(tokens[i]=="*")
{
x1 = st.top();
st.pop();
y1 = st.top();
st.pop();
st.push(y1*x1);
}
else if(tokens[i]=="/")
{
x1 = st.top();
st.pop();
y1 = st.top();
st.pop();
st.push(y1/x1);
}
else if(tokens[i]=="+")
{
x1 = st.top();
st.pop();
y1 = st.top();
st.pop();
st.push(y1+x1);
}
else if(tokens[i]=="-")
{
x1 = st.top();
st.pop();
y1 = st.top();
st.pop();
st.push(y1-x1);
}
else
{
x = 0;
int j = 0;
int xi=0;
if(tokens[i][j]=='-')
{
j++;
xi = 1;
}
for(j;j<tokens[i].size();j++)
{
x = x*10+tokens[i][j]-'0';
}
if(xi)
x *= -1;
st.push(x);
}
}
return st.top();
}
};
3.3.stack模拟实现
我们如果按照之前的思路来实现的话,大概率会实现成这样
cpp
#pragma once
#include <iostream>
namespace qiu
{
template<class T>
class stack
{
private:
T* _a;
size_t _size;
size_t _capacity;
};
}
但我们学完适配器后其实完全不用这么写,因为stack这个东西完全可以用vector来进行封装转换,因为栈主要支持的就三个东西,一个入栈,一个出栈,一个查询栈顶元素,那入栈不就对应vector的尾插,出栈不就对应vector的尾删,查询栈顶元素不就是查询vector内部最后一个元素
那我们就可以写成如下样子
cpp
#pragma once
#include <iostream>
namespace qiu
{
template<class T>
class stack
{
private:
vector<T> _con;
};
}
但是这个时候,其实我们相当于又把栈写死了,因为只能通过vector容器来构造栈,那么我们可以用一个模板参数来代表容器,这种方式也很贴近底层的实现方式,也就是如下代码
cpp
#pragma once
#include <iostream>
namespace qiu
{
template<class T,class Container>
class stack
{
private:
Container _con;
};
}
这样,我们就可以通过传入不同的容器类型来形成不同的栈。
这就是container适配转换成stack。当然,我们也不用写构造函数了,因为Container肯定是个自定义类型,编译器默认生成的构造函数会调用自定义类型的构造,所以我们不用管
接下来,我们来实现一下核心接口
cpp
#pragma once
#include <iostream>
namespace qiu
{
template<class T,class Container>
class stack
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_back();
}
T& top()
{
return _con.back();
}
const T& top() const
{
return _con.back();
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
private:
Container _con;
};
}
至此,stack模拟实现就完成了,是不是超级简单
我们可以用一下这个栈
cpp
#define _CRT_SECURE_NO_WARNINGS
#include "stack.h"
#include <vector>
#include <list>
using namespace qiu;
int main()
{
stack<int, std::vector<int>> st1;
stack<std::string, std::list<std::string>> st2;
st1.push(1);
st1.push(2);
st1.push(3);
while (!st1.empty())
{
std::cout << st1.top() << " ";
st1.pop();
}
std::cout << std::endl;
st2.push("xixi");
st2.push("haha");
st2.push("aeiou");
while (!st2.empty())
{
std::cout << st2.top() << " ";
st2.pop();
}
return 0;
}

我们之前一般使用stack的时候,大多是这么使用的,stack<int> st;想要实现这种效果只要在第二个模板参数加个缺省参数即可,如下
cpp
template<class T,class Container = vector<T>>
STL底层赋的缺省参数是deque<T>
拓展:按需实例化
这里再拓展一个点,按需实例化,这个在前面章节也讲过,只是那个时候没有合适的样例,这里可以细致讲讲
我们可以先看这个代码
stack.h
cpp
#pragma once
#include <iostream>
namespace qiu
{
template<class T,class Container = vector<T>>
class stack
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_back();
}
T& top()
{
return _con.back();
}
const T& top() const
{
return _con.back();
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
private:
Container _con;
};
}
test.cpp
cpp
#define _CRT_SECURE_NO_WARNINGS
#include "stack.h"
#include <vector>
#include <list>
using namespace qiu;
using namespace std;
int main()
{
stack<int> st1;
st1.push(1);
st1.push(2);
st1.push(3);
while (!st1.empty())
{
cout << st1.top() << " ";
st1.pop();
}
cout << endl;
return 0;
}
运行我们可以发现是没有问题的,但是我们.h头文件里并没有包vector,为什么不报错呢,因为我们实现的stack是模板,编译器检查是很宽泛的,这个模板实例化的时候是在main函数内部了,.h头文件经过编译链接已经合并了,这个时候向上找使能找到vector头文件的
当然,如果找不到就会报错了,就比如这种代码
cpp
#pragma once
#include <iostream>
namespace qiu
{
template<class T,class Container = vector<T>>
class stack
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_back();
}
T& top()
{
return _con.back();
}
const T& top() const
{
return _con.back();
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
private:
Container _con;
};
void func()
{
int x = 0;
cout << x << endl;
}
}
如果我们在 .cpp中头文件是这个样子的,就会编译报错,因为就算编译链接过去,也是替换在了"stack.h"那个位置,往上找找不到using namespace std;
cpp
#define _CRT_SECURE_NO_WARNINGS
#include "stack.h"
#include <vector>
#include <list>
using namespace qiu;
using namespace std;
但如果这个头文件在std之下,就不会报错了,如下
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <vector>
#include <list>
using namespace std;
#include "stack.h"
using namespace qiu;
接下来就是重点------按需实例化
我们先看代码
cpp
#pragma once
#include <iostream>
namespace qiu
{
template<class T,class Container = vector<T>>
class stack
{
public:
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
_con.pop_front();//这里改了,vector没有pop_front接口
}
T& top()
{
return _con.back();
}
const T& top() const
{
return _con.back();
}
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
private:
Container _con;
};
}
如果我们不调用pop,就不会编译错误

但vector是没有pop_front接口的,这个时候我们调用pop就会发现编译报错了

这正是因为按需实例化的原因,没调用的函数,编译器不会实例化,只会检查大体有没有问题
所以有的时候,模板实现一直没问题,突然有一次调用出问题了,很可能就是这个接口之前一直没有使用过,然后这个接口内部有点问题
4.queue的介绍和使用
4.1.queue的介绍
和stack一样,如下图

4.2.queue的使用

还是一样,核心接口就这么些
要用需要加上#include <queue>头文件
这里用道题练练应用
AC码
cpp
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> ans;
if(root==nullptr)
return ans;
int _size = 1;
queue<TreeNode*> q;
q.push(root);
while(!q.empty())
{
vector<int> v;
while(_size--)
{
TreeNode* p = q.front();
if(p->left!=nullptr)
q.push(p->left);
if(p->right!=nullptr)
q.push(p->right);
q.pop();
v.push_back(p->val);
}
_size = q.size();
ans.push_back(v);
}
return ans;
}
};
4.3.queue的模拟实现
这个其实就和上面的stack一样,实现起来很简单,如下
cpp
#pragma once
#include <iostream>
#include <cassert>
#include <deque>
namespace qiu
{
template<class T,class Container = deque<T>>
class queue
{
public:
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
assert(size());
_con.pop_front();
}
T& front()
{
return _con.front();
}
const T& front() const
{
return _con.front();
}
T& back()
{
return _con.back();
}
const T& back() const
{
return _con.back();
}
private:
Container _con;
};
}
需要注意的是,队列而言就不能用vector了,因为队列是一端进一端出,如果用vector,出队列的时间复杂度是O(N)了
我们如果用vector来作为Container,一开始不会报错,直到我们调用pop才会报错,因为vector是没有pop_front这个操作的,这也体现了 前面我们所讲的按需实例化问题
5.priority_queue的介绍和使用
5.1.priority_queue的介绍

priority_queue我们平时常称优先队列,这个容器适配器相比于前面俩个而言会复杂一点
它的接口和stack的接口非常的像

push就是正常压入进去,这里最需要注意的是pop和top,它要选择优先级高的进行pop和top(priority_queue默认情况下是大的优先级高),如果想控制小的优先级高就得用到一个叫仿函数的东西。
priority_queue的底层就是我们之前的堆,堆的底层就是数组(用来表示完全二叉树),所以 priority_queue的默认适配容器是vector

可以用deque吗,可以,但是相比vector效率会低很多,因为堆操作会不断对中间部分的数据进行操作
5.2.priority_queue的使用
注意:用priority_queue只需要包queue头文件就行,priority_queue是包含在queue头文件里的
具体使用其实就和前面也一样,只是top和pop需要注意,不是删除头元素和输出头元素,是找优先级最高的元素操作
实例如下
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <queue>
using namespace std;
int main()
{
priority_queue<int> pq;
pq.push(2);
pq.push(1);
pq.push(3);
pq.push(5);
pq.push(4);
while (pq.size())
{
cout << pq.top()<<" ";
pq.pop();
}
return 0;
}

如果我们想让他变成小堆,就如下
cpp
priority_queue<int, vector<int>, greater<int>> pq;

写个题练练手
215. 数组中的第K个最大元素 - 力扣(LeetCode)
AC码
cpp
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int,vector<int>,greater<int>> pq;
for(auto x:nums)
{
if(pq.size()==k&&x>pq.top())
{
pq.pop();
pq.push(x);
}
else if(pq.size()<k)
pq.push(x);
}
return pq.top();
}
};
5.3.priority_queue的模拟实现
我们这里先不加仿函数,把基本结构写清楚
cpp
#pragma once
#include <iostream>
#include <vector>
using namespace std;
namespace qiu
{
template<class T,class Container = vector<T>>
class priority_queue
{
public:
void push(const T& x)
{
}
void pop()
{
}
T& top()
{
}
const T& top() const
{
}
bool empty() const
{
return _con.empty();
}
size_t size() const
{
return _con.size();
}
private:
Container _con;
};
}
push
因为底层是堆,所以按照堆的数据结构,我们先对vector尾插,然后进行向上调整法成堆结构(因为priority_queue默认大的优先级高,所以我们现在就按如果大就向上调整来实现)
cpp
void push(const T& x)
{
_con.push_back(x);
size_t i = _con.size() - 1;
while (i)
{
if (_con[i] > _con[(i - 1) / 2])
{
swap(_con[i], _con[(i - 1) / 2]);
i = (i - 1) / 2;
}
else
break;
}
}
top
因为底层是堆,所以堆顶元素就是我们想要的元素,如下
cpp
T& top()
{
return _con[0];
}
const T& top() const
{
return _con[0];
}
注意:如果容器为空 ,调用top访问_con0就属于未定义行为,因为STL库中的top也没有对这种情况处理,所以我们也不处理
pop
这个就是把堆顶元素和最后一个元素交换,然后对vector------pop_back,随后再对堆顶元素向下调整,重新调整为堆结构
cpp
void pop()
{
std::swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
size_t i = 0;
size_t lin = i * 2 + 1;
while (lin < _con.size())
{
if (lin + 1 < _con.size() && _con[lin] < _con[lin + 1])
{
++lin;
}
if (_con[i] < _con[lin])
{
std::swap(_con[i], _con[lin]);
i = lin;
lin = lin * 2 + 1;
}
else
break;
}
}
无仿函数版实现
cpp
#pragma once
#include <iostream>
#include <cassert>
#include <deque>
#include "priority_queue.h"
namespace qiu
{
template<class T,class Container = deque<T>>
class queue
{
public:
size_t size() const
{
return _con.size();
}
bool empty() const
{
return _con.empty();
}
void push(const T& x)
{
_con.push_back(x);
}
void pop()
{
assert(size());
_con.erase(_con.begin());
}
T& front()
{
return _con.front();
}
const T& front() const
{
return _con.front();
}
T& back()
{
return _con.back();
}
const T& back() const
{
return _con.back();
}
private:
Container _con;
};
}
我们测试一下
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include "queue.h"
using namespace std;
int main()
{
qiu::priority_queue<int> pq;
pq.push(3);
pq.push(1);
pq.push(2);
pq.push(5);
pq.push(4);
while (pq.size())
{
cout << pq.top() << " ";
pq.pop();
}
return 0;
}

但是上面的这种只实现了单一的排序,如果我想要小根堆,上述代码就无法实现了,这个时候,为了防止代码的冗余,写俩份基本没什么区别的代码,C++引入了一个叫仿函数的概念,我们先来看仿函数是什么
仿函数(重点)
首先,仿函数是一个类,这个类里面有()运算符重载(函数调用运算符重载)。就比如说,我们想要实现整型的比大小,实例如下,这个operator()重载的()是函数调用的时候参数列表的()。我们先前在前面讲过,类内如果没有成员变量的时候,类的大小为1,仿函数就普遍是这种情况,因为一般仿函数对应的类的内部基本就是只有一个operator()的重载
cpp
class Less
{
public:
bool operator()(int x, int y)
{
return x < y;
}
};
我们也可以把仿函数改造成类模板
cpp
template<class T>
class Less
{
public:
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
那么,仿函数怎么用呢,我们举个实例来看
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include "queue.h"
using namespace std;
template<class T>
class Less
{
public:
bool operator()(const T& x, const T& y)
{
return x < y;
}
};
int main()
{
Less<int> ls;
cout << ls(1, 2) << endl;
return 0;
}
如果我们不看Less<int> ls,只看ls(1,2),我们就会以为ls是个函数,但其实ls是个对象,这也是我们为什么把这个取名为仿函数的原因
那我们除了可以写一个Less的还能写个Greater的
cpp
template<class T>
class Greater
{
public:
bool operator()(const T& x, const T& y)
{
return x > y;
}
};
总结 :仿函数本质是一个类,这个类重载了operator(),他的对象可以像函数一样使用,所以仿函数的对象我们可以称为函数对象,但本质的底层其实还是调用了 operator()
目前来看,这个东西没什么用,我这里举个例子来带你们初步感受下这个的妙用
假设我要做一个冒泡排序,我们可以这么实现
cpp
template<class T>
void Sort(int* a, size_t n, T cmp)
{
for (int j = 0; j < n; j++)
{
int flag = 0;
for (int i = 1; i < n - j; ++i)
{
if (cmp(a[i - 1], a[i]))
{
swap(a[i - 1], a[i]);
flag = 1;
}
}
if (!flag)
break;
}
}
我们可以把判断交给仿函数,这样我们就可以通过一个函数模板,实现出不同的排序效果,如下

那么,仿函数懂了后,接下来,我对priority_queue优化成有仿函数版的实现
有仿函数版实现
cpp
#pragma once
#include <iostream>
#include <vector>
namespace qiu
{
template<class T,class Container = std::vector<T>,class Compare = std::less<T>>
class priority_queue
{
public:
void push(const T& x)
{
_con.push_back(x);
Up();
}
void pop()
{
std::swap(_con[0], _con[_con.size() - 1]);
_con.pop_back();
Down();
}
T& top()
{
return _con[0];
}
const T& top() const
{
return _con[0];
}
bool empty() const
{
return _con.empty();
}
size_t size() const
{
return _con.size();
}
private:
Container _con;
Compare cmp;
void Up()
{
size_t i = _con.size() - 1;
while (i)
{
if (cmp(_con[(i - 1) / 2], _con[i]))
{
std::swap(_con[i], _con[(i - 1) / 2]);
i = (i - 1) / 2;
}
else
break;
}
}
void Down()
{
size_t i = 0;
size_t lin = i * 2 + 1;
while (lin < _con.size())
{
if (lin + 1 < _con.size() && cmp(_con[lin], _con[lin + 1]))
{
++lin;
}
if (cmp(_con[i], _con[lin]))
{
std::swap(_con[i], _con[lin]);
i = lin;
lin = lin * 2 + 1;
}
else
break;
}
}
};
}
我们测试一下
cpp
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include "queue.h"
using namespace std;
int main()
{
qiu::priority_queue<int, vector<int>, greater<int>> pq;
pq.push(1);
pq.push(3);
pq.push(2);
pq.push(5);
pq.push(7);
pq.push(6);
while (pq.size())
{
cout << pq.top() << " ";
pq.pop();
}
cout << endl;
qiu::priority_queue<int, vector<int>> pq1;
pq1.push(1);
pq1.push(3);
pq1.push(2);
pq1.push(5);
pq1.push(7);
pq1.push(6);
while (pq1.size())
{
cout << pq1.top() << " ";
pq1.pop();
}
return 0;
}
没有问题

6.结语
那么,C++STL部分stack,queue和priority_queue的内容就全部讲解完毕啦,希望以上内容对你有所帮助,感谢观看,若觉得写的还可以,可以分享给朋友一起来看哦,毕竟一起进步更有动力嘛,当然能关注一下就更好啦。
