【C++】30h速成C++从入门到精通(stack、queuepriority_queue以及deque介绍)
创始人
2024-05-29 17:48:03
0

stack

stack的介绍

https://cplusplus.com/reference/stack/stack/?kw=stack

  1. stack是一种容器适配器,专门在具有后进先出操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。

  1. stack是作为容器适配器被实现的,容器适配器即是对特定类作为其底层的容器,并且提供一组特定的成员函数来访问其元素,将特定类作为其底层,元素特定容器的尾部即栈顶被压入和弹出。

  1. stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下操作:

  • empty:判空操作

  • back:获取尾部元素操作

  • push_back:尾部插入元素操作

  • pop_back:尾部删除元素操作

  1. 标准容器vector、deque、list均符合这些需求,默认情况下,如果没有stack指定特定的底层容器,默认情况下使用deque

stack的使用

函数说明

接口说明

stack()

构造空的栈

empty()

检测stack是否为空

size()

返回stack中元素的个数

top()

返回栈顶元素的引用

push()

将元素vai踏入stack

pop()

将stack中尾部的元素弹出

最小栈:

class MinStack
{
public: void push(int x){ // 只要是压栈,先将元素保存到_elem中_elem.push(x);// 如果x小于_min中栈顶的元素,将x再压入_min中if(_min.empty() || x <= _min.top())_min.push(x);}void pop(){// 如果_min栈顶的元素等于出栈的元素,_min顶的元素要移除if(_min.top() == _elem.top())_min.pop();_elem.pop();}int top(){return _elem.top();}int getMin(){return _min.top();}
private:// 保存栈中的元素std::stack _elem;// 保存栈的最小值std::stack _min;
};

栈的弹出压入序列

class Solution {
public:bool IsPopOrder(vector pushV,vector popV) {//入栈和出栈的元素个数必须相同if(pushV.size() != popV.size())return false;// 用s来模拟入栈与出栈的过程int outIdx = 0;int inIdx = 0;stack s;while(outIdx < popV.size()){// 如果s是空,或者栈顶元素与出栈的元素不相等,就入栈while(s.empty() || s.top() != popV[outIdx]){if(inIdx < pushV.size())s.push(pushV[inIdx++]);elsereturn false;}// 栈顶元素与出栈的元素相等,出栈s.pop();outIdx++;}return true;}
};

逆波兰表达式求值

class Solution {
public:int evalRPN(vector& tokens) {stack s;for (size_t i = 0; i < tokens.size(); ++i){string& str = tokens[i];// str为数字if (!("+" == str || "-" == str || "*" == str || "/" == str)){s.push(atoi(str.c_str()));}else{// str为操作符int right = s.top();s.pop();int left = s.top();s.pop();switch (str[0]){case '+':s.push(left + right);break;case '-':s.push(left - right);break;case '*':s.push(left * right);break;case '/':// 题目说明了不存在除数为0的情况s.push(left / right);break;}}}return s.top();}
};

stack的模拟实现

从栈的接口可以看出,栈实际是一种特殊的vector,因此使用vector完全可以模拟实现stack。

#include
namespace bite
{templateclass stack{public:stack() {}void push(const T& x) {_c.push_back(x);}void pop() {_c.pop_back();}T& top() {return _c.back();}const T& top()const {return _c.back();}size_t size()const {return _c.size();}bool empty()const {return _c.empty();}private:std::vector _c;};
}

queue

queue的介绍

https://cplusplus.com/reference/queue/queue/

  1. 队列是一种容器适配器,专门用于在FIFO上下文(先进先出)中操作,其中从容器一段插入元素,另一端提取元素。

  1. 队列作为容器适配器实现,容器适配器即将特定容器类进行封装作为其底层容器类,queue提供一组特定的成员函数访问其元素,元素从队尾入队列,从队头出队列。

  1. 底层容器可以是标准容器类模板之一,也可以是其他专门设计的容器类,该底层容器应至少支持以下操作:

  • empty:判空操作

  • size:返回队列中有效元素的个数

  • front:返回队头元素的引用

  • back:返回队尾元素的引用

  • push_back:在队列尾部入队列

  • pop_front:在队列头部出队列

  1. 标准容器类deque和list满足了这些要求,默认情况下,如果没有为queue实例化指定容器类,则使用标准容器queue。

queue的使用

函数声明

接口说明

queue()

构造空的队列

empty()

检测队列是否为空,是返回ture,否则返回false

size()

返回队列中有效元素的个数

front()

返回队头元素的引用

back()

返回队尾元素的引用

push()

将队尾元素val入队列

pop()

将队头元素出列

queue的模拟实现

因为queue的接口中存在头插和尾插,因此使用vector来封装效率太低,故可以借助list来模拟queue,具体如下:

#include 
namespace bite
{templateclass queue{public:queue() {}void push(const T& x) {_c.push_back(x);}void pop() {_c.pop_front();}T& back() {return _c.back();}const T& back()const {return _c.back();}T& front() {return _c.front();}const T& front()const {return _c.front();}size_t size()const {return _c.size();}bool empty()const {return _c.empty();}private:std::list _c;};
}

priority_queue

priority_queue的介绍和使用

https://cplusplus.com/reference/queue/priority_queue/

  1. 优先队列是一种容器适配器,根据严格的弱排序标准,他的第一个元素总是他所包含的元素中最大的。

  1. 此上下文类似于堆,在队中可以随时插入元素,并且只能检索最大堆元素(优先队列中位于顶部的元素)。

  1. 优先队列被时限为容器适配器,容器适配器即将特定容器类封装作为其底层容器类,queue提供一组特定成员函数来访问其元素。元素从特定容器的”尾部“弹出,其称为优先队列的顶部。

  1. 底层容器可以是任何标准容器类模板,也可以是其他特定设计的容器类,容器应该可以通过随机的访问迭代器访问,并支持以下操作:

  • empty():检测容器是否为空

  • size():返回容器中有效元素的个数

  • front():返回容器中第一个元素的引用

  • push_back:在容器尾部插入元素

  • pop_back:删除在容器尾部的元素

  1. 标准容器vector和deque满足这些需求默认情况下,如果没有为特定的priority_queue类实例化指定容器类,则使用vector。

  1. 需要支持随机访问迭代器,以便始终在内部保持堆结构,容器适配器通过在需要时自动调用算法函数make_heap、push_heap和pop_heap来自动完成此操作。

priority_queue的使用

优先级队列默认使用vector作为其底层存放数据的容器,在vector上有使有了堆算法将vector中元素构造成堆的结构,因此priority_queue就是堆,所有需要用到堆的位置,都可以考虑使用priority_queue,注意:默认情况下priority_queue是大堆。

函数声明

接口说明

priority_queue()/priority_queue(first,last)

构造一个空的优先级队列

empty()

检测优先级队列是否为空,是返回true,都则返回false

top()

返回优先级队列中最大(最小元素),即堆顶元素

push(x)

在优先级队列中插入元素x

pop()

删除优先级队列中最大(最小)元素。即堆顶元素

【注意】

  1. 默认情况下,priority_queue是大堆

#include 
#include 
#include  // greater算法的头文件
void TestPriorityQueue()
{// 默认情况下,创建的是大堆,其底层按照小于号比较vector v{3,2,7,6,0,4,1,9,8,5};priority_queue q1;for (auto& e : v)q1.push(e);cout << q1.top() << endl;// 如果要创建小堆,将第三个模板参数换成greater比较方式priority_queue, greater> q2(v.begin(), v.end());cout << q2.top() << endl;
}
  1. 如果在priority_queue中放自定义类型的数据,用户需要在自定义类型中提供> 或者< 的重载。

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1): _year(year), _month(month), _day(day){}bool operator<(const Date& d)const{return (_year < d._year) ||(_year == d._year && _month < d._month) ||(_year == d._year && _month == d._month && _day < d._day);}bool operator>(const Date& d)const{return (_year > d._year) ||(_year == d._year && _month > d._month) ||(_year == d._year && _month == d._month && _day > d._day);}friend ostream& operator<<(ostream& _cout, const Date& d){_cout << d._year << "-" << d._month << "-" << d._day;return _cout;}
private:int _year;int _month;int _day;
};
void TestPriorityQueue()
{// 大堆,需要用户在自定义类型中提供<的重载priority_queue q1;q1.push(Date(2018, 10, 29));q1.push(Date(2018, 10, 28));q1.push(Date(2018, 10, 30));cout << q1.top() << endl;// 如果要创建小堆,需要用户提供>的重载priority_queue, greater> q2;q2.push(Date(2018, 10, 29));q2.push(Date(2018, 10, 28));q2.push(Date(2018, 10, 30));cout << q2.top() << endl;
}

在OJ中的使用

数组中第k大个元素

class Solution {
public:int findKthLargest(vector& nums, int k) {// 将数组中的元素先放入优先级队列中priority_queue p(nums.begin(), nums.end());// 将优先级队列中前k-1个元素删除掉for(int i= 0; i < k-1; ++i){p.pop();}return p.top();}
};

priority_queue的模拟实现

通过对priority_queue的底层结构就是堆,因此此处只需对堆进行通用的封装即可。

#include 
// priority_queue--->堆
namespace bite
{templatestruct less{bool operator()(const T& left, const T& right){ return left < right; }};templatestruct greater{bool operator()(const T& left, const T& right){ return left > right; }};template, class Compare=less>class priority_queue{public:// 创造空的优先级队列priority_queue(): c(){}templatepriority_queue(Iterator first, Iterator last): c(first, last){// 将c中的元素调整成堆的结构int count = c.size();int root = ((count - 2) >> 1);for (; root >= 0; root--)AdjustDown(root);}void push(const T& data){c.push_back(data);AdjustUP(c.size() - 1);}void pop(){if (empty())return;swap(c.front(), c.back());c.pop_back();AdjustDown(0);}size_t size()const{ return c.size(); }bool empty()const{ return c.empty(); }// 堆顶元素不允许修改,因为:堆顶元素修改可以会破坏堆的特性const T& top()const{ return c.front(); }private:// 向上调整void AdjustUP(int child){int parent = ((child - 1) >> 1);while (child){if (Com()(c[parent], c[child])){swap(c[child], c[parent]);child = parent;parent = ((child - 1) >> 1);}else{return;}}}// 向下调整void AdjustDown(int parent){int child = parent * 2 + 1;while (child < c.size()){// 找以parent为根的较大的孩子if (child + 1 < c.size() && Com()(c[child], c[child+1]))child += 1;// 检测双亲是否满足情况if (Com()(c[parent], c[child])){swap(c[child], c[parent]);parent = child;child = parent * 2 + 1;}elsereturn;}}private:Container c;};
}

容器适配器

什么是适配器

适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该种模式是将一个类的接口转换成客户希望的另外一个接口。

STL标准库中stack和queue的底层结构

虽然stack和queue中也可以存放元素,但在STL中并没有将其划分在容器的行列,而是将其称为容器适配器,这是因为stack和队列只是对其他容器的接口进行了包装,STL中stack和queue默认使用deque,比如:

deque的简单介绍

deque原理

deque(双端队列):是一种双开口的"连续"空间的数据结构,双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1),与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高。

deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的,实际deque类似于一个动态的二维数组,其底层结构如下图所示:

双端队列底层是一段假象的连续空间,实际是分段连续的,为了维护其“整体连续”以及随机访问的假象,落在了deque的迭代器身上,因此deque的迭代器设计就比较复杂.

deque缺陷

与vector比较,deque的优势是:头部插入和删除时,不需要搬移元素,效率特别高,而且在扩容时,也不需要搬移大量的元素,因此其效率是必vector高的。

与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段。

但是,deque有一个致命缺陷:不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,导致效率低下,而序列式场景中,可能需要经常遍历,因此在实际中,需要线性结构时,大多数情况下优先考虑vector和list,deque的应用并不多,而目前能看到的一个应用就是,STL用其作为stack和queue的底层数据结构。

为什么选择deque作为stack和queue的底层默认容器

stack是一种后进先出的特殊线性数据结构,因此只要具有push_back()和pop_back()操作的线性结构,都可以作为stack的底层容器,比如vector和list都可以;queue是先进先出的特殊线性数据结构,只要具有push_back和pop_front操作的线性结构,都可以作为queue的底层容器,比如list。但是STL中对stack和queue默认选择deque作为其底层容器,主要是因为:

  1. stack和queue不需要遍历(因此stack和queue没有迭代器),只需要在固定的一端或者两端进行操作。

  1. 在stack中元素增长时,deque比vector的效率高(扩容时不需要搬移大量数据);queue中的元素增长时,deque不仅效率高,而且内存使用率高。

结合了deque的优点,而完美的避开了其缺陷。

STL标准库中对于stack和queue的模拟实现

stack模拟实现

#include
namespace bite
{template>//template>//template>class stack{public:stack() {}void push(const T& x) {_c.push_back(x);}void pop() {_c.pop_back();}T& top() {return _c.back();}const T& top()const {return _c.back();}size_t size()const {return _c.size();}bool empty()const {return _c.empty();}private:Con _c;};
}

queue的模拟实现

#include
#include 
namespace bite
{template>//template>class queue{public:queue() {}void push(const T& x) {_c.push_back(x);}void pop() {_c.pop_front();}T& back() {return _c.back();}const T& back()const {return _c.back();}T& front() {return _c.front();}const T& front()const {return _c.front();}size_t size()const {return _c.size();}bool empty()const {return _c.empty();}private:Con _c;};
}

相关内容

热门资讯

未来的作文 关于未来的作文(精选40篇)  无论是在学校还是在社会中,大家都不可避免地会接触到作文吧,作文是一种...
远足的感受作文 远足的感受作文3篇  在平平淡淡的日常中,大家都尝试过写作文吧,作文可分为小学作文、中学作文、大学作...
未来建筑作文 关于未来建筑作文五篇  在我们平凡的日常里,大家都不可避免地要接触到作文吧,通过作文可以把我们那些零...
写一处自然景观作文 写一处自然景观作文(精选10篇)  在平平淡淡的日常中,大家对作文都不陌生吧,作文是人们以书面形式表...
绿豆观察日记 关于绿豆观察日记范文7篇  一天又结束了,心中一定有不少感想,立即行动起来写一篇日记吧。那么什么样的...
魔法森林童话作文 魔法森林童话作文3篇  在日常学习、工作抑或是生活中,大家都写过作文,肯定对各类作文都很熟悉吧,作文...
种植向日葵观察日记 种植向日葵观察日记(精选6篇)  即将要到一天的结尾了,相信你会领悟到不少东西,不妨坐下来好好写写日...
写给十年后的自己作文 写给十年后的自己作文(精选26篇)  在平平淡淡的日常中,大家总免不了要接触或使用作文吧,作文是从内...
童话王国作文 【精选】童话王国作文8篇  在平日的学习、工作和生活里,许多人都写过作文吧,借助作文可以宣泄心中的情...
我的理想优秀小学生作文 我的理想优秀小学生作文(精选52篇)  在日常学习、工作抑或是生活中,大家都写过作文吧,写作文可以锻...
我的课余生活作文400字 我的课余生活作文400字(通用62篇)  在日常学习、工作和生活中,大家对作文都不陌生吧,作文是一种...
金乌和玉兔趣味童话故事 金乌和玉兔趣味童话故事  金色的太阳里,住着一只长着三只脚的金色乌鸦。银色的月亮里,住着一只洁白的玉...
未来的交通作文 未来的交通作文(15篇)  在平时的学习、工作或生活中,许多人都写过作文吧,借助作文可以宣泄心中的情...
黄豆的观察日记 黄豆的观察日记(通用31篇)  不知不觉中一天又要结束了,我们一定有不少所感触的事情吧,让我们今天做...
展开想象的翅膀作文 展开想象的翅膀作文3篇  在日复一日的学习、工作或生活中,说到作文,大家肯定都不陌生吧,作文是通过文...
写给初三的自己作文 写给初三的自己作文9篇  在平平淡淡的日常中,说到作文,大家肯定都不陌生吧,借助作文人们可以实现文化...
经典童话故事 经典童话故事(精选27个)  童话故事是指儿童文学的一种体裁,童话中丰富的想象和夸张可以活跃你的思维...
我学会了自己去上学作文 我学会了自己去上学作文  在日常学习、工作或生活中,大家总少不了接触作文吧,作文是通过文字来表达一个...
答自己问作文 答自己问作文(精选25篇)  在平日的学习、工作和生活里,大家都不可避免地会接触到作文吧,写作文是培...
我学会了游泳作文 我学会了游泳作文(通用10篇)  在平平淡淡的学习、工作、生活中,许多人都写过作文吧,作文根据写作时...