stack+queue

目录

适配器

介绍

分类 

容器适配器

迭代器适配器

deque

介绍

特点 

底层结构

优势

缺点

介绍

模拟实现

注意点

代码

stack

queue

算法题示例

栈的压入/弹出序列

题目

思路

代码

最小栈

题目

思路

代码

逆波兰数 (后缀 转 中缀)

题目

思路

代码

中缀 转 后缀


适配器

介绍

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

在C++的标准模板库(STL)中,有几种适配器,它们是一些容器或函数对象的加工包装,提供不同的接口和功能,用于适应特定的需求

分类 

STL中的适配器可以分为两类:容器适配器和迭代器适配器

容器适配器

容器适配器用来包装不同种类的容器,并提供统一的接口(其实就是将我们常见的容器包装一下,用来实现其他容器的功能)

虽然也能存放元素,但并不属于容器

常见的容器适配器有:

  • stack(栈): 用于实现后进先出(LIFO)的数据结构,基于另一个容器(默认是deque)来实现
  • stack+queue_第1张图片
  • queue(队列): 用于实现先进先出(FIFO)的数据结构,也是基于另一个容器(默认是deque)来实现
  • priority_queue(优先队列): 用于实现优先级排序的数据结构,基于另一个容器(默认是vector)来实现

迭代器适配器

stack+queue_第2张图片

(这个我目前还不清楚,看了下示例代码,感觉好麻烦的样子) 

deque

介绍

  • 双端队列,全称为 "double-ended queue" , 是C++标准库中提供的一种容器,是一种双开口的"连续"空间的数据结构,支持在两端进行高效插入和删除操作的数据结构
  • 双开口的含义是:可以在头尾两端进行插入和删除操作,且时间复杂度为O(1)
  • stack+queue_第3张图片
  • 与vector比较,头插效率高,不需要搬移元素;与list比较,空间利用率比较高属于是六边形战士,但都不精 

特点 

既可以支持像list一样的头插头删尾插尾删,又支持vector的[ ]操作,以及它支持的迭代器是随机迭代器

  • stack+queue_第4张图片

底层结构

deque并不是真正连续的空间,而是由一段段连续的小空间拼接而成的
实际deque类似于一个动态的二维数组 ,用一个指针数组指向多个buf
stack+queue_第5张图片

优势

  • 头部插入和删除时,不需要搬移元素,效率特别高
  • 在扩容时,也不需要搬移大量的元素,因此其效率是比vector高的
  • 与list比较,其底层是连续空间,空间利用率比较高,不需要存储额外字段

缺点

  • 不适合遍历,因为在遍历时,deque的迭代器要频繁的去检测其是否移动到某段小空间的边界,以及判断在哪个空间上 ,导致效率低下
  • 而序列式场景中,可能需要经常遍历 / 随机访问
  • 所以在实际中需要线性结构时,大多数情况下会优先考虑vector和list 
  • deque适合需要频繁头插尾插头删尾删的情况 -- 也就是栈/队列,虽然他俩出数据方式不同,但都只会访问头尾位置的数据

介绍

stack(栈): 用于实现后进先出(LIFO)的数据结构,基于另一个容器(默认是deque)来实现

queue(队列): 用于实现先进先出(FIFO)的数据结构,也是基于另一个容器(默认是deque)来实现

模拟实现

注意点

适配器使用的容器的传参(默认deque)

构造/析构函数不需要自己实现,成员是自定义类型,会调用它自己的构造/析构的

代码

stack

#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;
    };
};

queue

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)

stack+queue_第6张图片

思路

一个压入顺序可以对应多个弹出顺序

  • 如果用压入判断弹出,就太难了,有特别特别多的序列
  • 但是如果用 给定的弹出 来判断 是否符合压入 ,是可以一一对应的

代码

    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)

stack+queue_第7张图片

思路

如果用成员变量来记录最小元素,似乎可以

但如果将最小元素删除后,我们又该怎么拿到原先第二小的元素呢?栈不能被遍历耶

所以我们可以考虑用一个数组来储存每一次操作后栈的最小值

  • 并且为了方便拿到每次的最小值,可以用栈/队列来储存(可以直接调用接口)
  • 因为要拿到该状态下的最小值,所以肯定是出的尾部元素,所以选择栈

代码

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)

stack+queue_第8张图片

stack+queue_第9张图片

stack+queue_第10张图片

思路

题目意思是让我们将后缀转换为中缀,然后计算

从上面我们可以知道,我们可以遍历表达式符号,数字入栈,符号计算,这样遍历完后,栈中剩下的元素就是结果

代码

#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();
    }

中缀 转 后缀

我们可以从题目的例子看出来,数字的相对顺序是没有变的,那么数字其实可以不用去处理

符号的话,因为先进行的计算是优先级更高的表达式

  • 根据后缀转中缀的规律,我们需要将高优先级的放在前面,让低优先级的放在后面
  • 那么为了让高的先出,就得遇到高优先级的就入栈,这样出的早 ; 遇到低优先级的就出栈顶元素
  • 相等优先级的话,相邻的这两个计算式不会有顺序
  • 然后因为一般从左到右计算,所以直接将上一个运算符出栈即可
  • stack+queue_第11张图片

你可能感兴趣的:(c++,数据结构与算法,c++)