目录
适配器
介绍
分类
容器适配器
迭代器适配器
deque
介绍
特点
底层结构
优势
缺点
介绍
模拟实现
注意点
代码
stack
queue
算法题示例
栈的压入/弹出序列
题目
思路
代码
最小栈
题目
思路
代码
逆波兰数 (后缀 转 中缀)
题目
思路
代码
中缀 转 后缀
介绍
适配器是一种设计模式
- 设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结
- 该种模式是将一个类的接口 转换成 客户希望的另外一个接口
在C++的标准模板库(STL)中,有几种适配器,它们是一些容器或函数对象的加工包装,提供不同的接口和功能,用于适应特定的需求
分类
STL中的适配器可以分为两类:容器适配器和迭代器适配器
容器适配器
容器适配器用来包装不同种类的容器,并提供统一的接口(其实就是将我们常见的容器包装一下,用来实现其他容器的功能)
虽然也能存放元素,但并不属于容器
常见的容器适配器有:
- stack(栈): 用于实现后进先出(LIFO)的数据结构,基于另一个容器(默认是deque)来实现
- queue(队列): 用于实现先进先出(FIFO)的数据结构,也是基于另一个容器(默认是deque)来实现
- priority_queue(优先队列): 用于实现优先级排序的数据结构,基于另一个容器(默认是vector)来实现
迭代器适配器
(这个我目前还不清楚,看了下示例代码,感觉好麻烦的样子)
介绍
特点
既可以支持像list一样的头插头删尾插尾删,又支持vector的[ ]操作,以及它支持的迭代器是随机迭代器
底层结构
deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的实际deque类似于一个动态的二维数组 ,用一个指针数组指向多个buf
优势
- 头部插入和删除时,不需要搬移元素,效率特别高
- 在扩容时,也不需要搬移大量的元素,因此其效率是比vector高的
- 与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段
缺点
- 不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,以及判断在哪个空间上 ,导致效率低下
- 而序列式场景中,可能需要经常遍历 / 随机访问
- 所以在实际中需要线性结构时,大多数情况下会优先考虑vector和list
- deque适合需要频繁头插尾插头删尾删的情况 -- 也就是栈/队列,虽然他俩出数据方式不同,但都只会访问头尾位置的数据
stack(栈): 用于实现后进先出(LIFO)的数据结构,基于另一个容器(默认是deque)来实现
queue(队列): 用于实现先进先出(FIFO)的数据结构,也是基于另一个容器(默认是deque)来实现
适配器使用的容器的传参(默认deque)
构造/析构函数不需要自己实现,成员是自定义类型,会调用它自己的构造/析构的
#include
#include
#include
#include
using namespace std;
namespace bit
{
template > // con是适配器(也就是容器适配器,其他容器经过包装 -> stack)
class mystack
{
public:
mystack() {}
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.front();
}
size_t size() const
{
return _c.size();
}
bool empty() const
{
return _c.empty();
}
private:
Con _c;
};
};
namespace bit
{
template >
class myqueue
{
public:
myqueue() {}
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;
};
}
栈的压入、弹出序列_牛客题霸_牛客网 (nowcoder.com)
思路
一个压入顺序可以对应多个弹出顺序
- 如果用压入判断弹出,就太难了,有特别特别多的序列
- 但是如果用 给定的弹出 来判断 是否符合压入 ,是可以一一对应的
bool IsPopOrder(vector& pushV, vector& popV) {
std::stack s;
size_t i=0,n=pushV.size();
for(auto c:pushV){
s.push(c); //先按照压入顺序添加元素
while(!s.empty()&&i
155. 最小栈 - 力扣(LeetCode)
思路
如果用成员变量来记录最小元素,似乎可以
但如果将最小元素删除后,我们又该怎么拿到原先第二小的元素呢?栈不能被遍历耶
所以我们可以考虑用一个数组来储存每一次操作后栈的最小值
- 并且为了方便拿到每次的最小值,可以用栈/队列来储存(可以直接调用接口)
- 因为要拿到该状态下的最小值,所以肯定是出的尾部元素,所以选择栈
class MinStack {
public:
MinStack() {
}
void push(int val) {
s.push(val);
if(_min.empty()||val<=_min.top()){ //如果当前进入元素比最小元素小,也添加到_min里
//要注意等于的时候也要添加,不然如果pop掉最小元素,_min里就没有另一个最小元素了
_min.push(val);
}
}
void pop() {
if(s.top()==_min.top()){ //如果pop的是最小元素,不要忘记也要pop_min里的
_min.pop();
}
s.pop();
}
int top() {
return s.top();
}
int getMin() {
return _min.top();
}
private:
std::stack s;
std::stack _min;
};
150. 逆波兰表达式求值 - 力扣(LeetCode)
题目意思是让我们将后缀转换为中缀,然后计算
从上面我们可以知道,我们可以遍历表达式符号,数字入栈,符号计算,这样遍历完后,栈中剩下的元素就是结果
#include
int evalRPN(vector& tokens) {
std::string arr[5]={" ","+","-","*" , "/"}; //用于判断拿到的是哪个计算符号
std::stack s;
int num=0;
for(auto c:tokens){
int flag=1;
for(size_t i=0;i<5;++i){ //判断是否是符号
if(arr[i]==c){
flag=0;
}
}
if(flag==1){ //是数字 -- 要string转int,然后数字入栈
// std::istringstream ss(c);
// ss >> num;
num=stoi(c);
s.push(num);
}
else{ //拿到符号
int a=s.top();s.pop(); //先拿到的是右
int b=s.top();s.pop(); //
int ans=0;
size_t i=0;
for(;i<5;++i){ //拿到计算的数字后,还要确定计算方法
if(arr[i]==c){
break;
}
}
if(i==1){
ans=a+b;
}
else if(i==2){
ans=b-a;
}
else if(i==3){
ans=a*b;
}
else{
ans=b/a;
}
s.push(ans); //结果入栈
}
}
return s.top();
}
我们可以从题目的例子看出来,数字的相对顺序是没有变的,那么数字其实可以不用去处理
符号的话,因为先进行的计算是优先级更高的表达式