C++stack和queue及适配器的学习使用

Posted aaaaaaaWoLan

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++stack和queue及适配器的学习使用相关的知识,希望对你有一定的参考价值。

stack的介绍和使用

stack的介绍

stack的文档介绍
翻译:

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

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

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

    • empty:判空操作
    • back:获取尾部元素操作
    • push_back:尾部插入元素操作
    • pop_back:尾部删除元素操作
  4. 标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器,默认情况下使用deque。

stack的使用

函数说明接口说明
stack()构造空的栈
empty()检测stack是否为空
size()返回stack中元素的个数
top()返回栈顶元素的引用
push()将元素val压入stack中
pop()将stack中尾部的元素弹出

最小栈

用两个栈实现最小栈,一个st用来正常push、pop数据,另一个minst只用来push最小的数据,也就是如果minst为空,就将push入st的数据也放入minst中;如果minst不为空,则将push入st的数据与minst的top数据进行判断,如果该数据小于等于top,则push入minst,否则就不push

同理,pop时,如果st要pop的数据与minst的栈顶数据相等,则minst也执行pop

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<int> _elem;
    
    // 保存栈的最小值
    std::stack<int> _min;
    ;

栈的弹出压入序列

思路:分别遍历pushV和popV,先将pushV的一个数据压入栈st中,再与popV的首数据进行判断,如果相等,说明该数据需要出栈,如果不相等,则继续遍历pushV进行push,然后循环与popV对比,相等就指向st的pop,不相等就继续push,直到pushV被遍历完,如果st中的数据没有pop完,说明该出栈顺序不是对应该入栈序列的,如果最后st为空了,则说明该出栈序列对应入栈序列。

class Solution 
public:
    bool IsPopOrder(vector<int> pushV,vector<int> popV) 
        stack<int> st;
        int pushi = 0;
        int popi = 0;
        
        //根据pushV数据向st中push数据,当pushV中数据全push完时,出循环若st为空,则说明出栈顺序合法,否则就非法
        while (pushi < pushV.size())
        
            //先push数据
            st.push(pushV[pushi]);
            ++pushi;
            
            //如果st不为空且st.top等于popV数组的对应元素,说明该元素需要pop
            //如果等于,则继续push
            while (!st.empty() && st.top() == popV[popi])
            
                st.pop();
                ++popi;
            
        
        
        return st.empty();
    
;

逆波兰表达式求值

中缀、后缀表达式

以4 + 13 / 5为例,

中缀表达式就是我们人类常用的方式,也就是按优先级进行运算。而计算机不能像人类一样识别,只会从左向右读取

后缀表达式(逆波兰表达式)的特点是操作数顺序不变,操作符在操作数的后面,并且按优先级排列。所以4+13/5的后缀表达式就是4 13 5 / +

思路:遍历后缀表达式,将后缀表达式中的数字入栈,当遍历至运算符时,根据运算符都是二元的特点,将栈顶的数据pop并作为该运算符的右操作数,再pop栈顶的数据,将它作为该操作符的左操作数,然后将运算结果再push入栈。

以此遍历完整个后缀表达式

class Solution 
public:
    int evalRPN(vector<string>& tokens) 
        stack<int> st;

        int tokensi = 0;
        

        while (tokensi < tokens.size())
        
            //如果是数字就入栈
            if (tokens[tokensi] != "+" 
            && tokens[tokensi] != "-" 
            && tokens[tokensi] != "*" 
            && tokens[tokensi] != "/" )
            
                st.push(stoi(tokens[tokensi]));
            
            else//是运算符就进行计算
            
                string& str(tokens[tokensi]);
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();

                //用switch case语句来实现运算符的选择
                switch(str[0])//switch的参数不能是自定义类型
                
                    case '+':
                    
                        st.push(left + right);
                        break;
                    
                    case '-':
                    
                        st.push(left - right);
                        break;
                    
                    case '*':
                    
                        st.push(left * right);
                        break;
                    
                    case '/':
                    
                        st.push(left / right);                
                        break;
                       
                
            
            
            ++tokensi;
        

        return st.top();
        
    
;

中缀表达式转后缀表达式

与逆波兰表达式计算相反,将运算符入栈,操作数输出

  1. 当栈为空时,将操作符入栈
  2. 当栈不为空时,比较该操作符与栈顶操作符的优先级
    1. 如果该操作符优先级高于栈顶操作符,则将该操作符入栈
    2. 如果该操作符优先级低于或等于栈顶操作符,则将栈顶操作符出栈,并进行操作数的计算。再比较该操作符与新栈顶数据的优先级…以此类推

以4 + 13 / 5为例,它的后缀表达式是 4 13 5 / +。

  1. 遇到4时,将其输出至链表,得到链表内容:4
  2. 遍历至’+‘时,将’+'入栈,得到栈内容: +
  3. 再遇到13,将其输出,得到链表内容:4->13
  4. 再向后走遍历至’/’,’/‘的优先级高于’+’,所以’/'入栈,得到栈内容: + /
  5. 最后遇到5,将其输出,得到链表内容:4->13->5
  6. 遍历完之后,开始将栈中的运算符出栈,先输出’/'至链表,得到链表内容:4->13->5-> ‘+’,栈内容: +
  7. 将栈中的’+'弹出至链表,得到链表内容:4->13->5-> ‘/’ -> ‘+’

至此,就得到了后缀表达式。

练习:用两个栈实现队列

stack的模拟实现

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

#include<vector>
namespace ysj

    template<class T>
    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:
    std::vector<T> _c;
    ;
    

queue的介绍和使用

queue的介绍

queue的文档介绍

翻译:

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

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

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

    • empty:检测队列是否为空
    • size:返回队列中有效元素的个数
    • front:返回队头元素的引用
    • back:返回队尾元素的引用
    • push_back:在队列尾部入队列
    • pop_front:在队列头部出队列
  4. 标准容器类deque和list满足了这些要求。默认情况下,如果没有为queue实例化指定容器类,则使用标准容器deque。

queue的使用

函数声明接口说明
queue()构造空的队列
empty()检测队列是否为空,是返回true,否则返回false
size()返回队列中有效元素的个数
front()返回队头元素的引用
back()返回队尾元素的引用
push()在队尾将元素val入队列
pop()将队头元素出队列

练习:用队列实现栈

queue的模拟实现

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

#include <list>
namespace bite

    template<class T>
    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:
    std::list<T> _c;
    ;


priority_queue的介绍和使用

priority_queue的介绍

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来自动完成此操作。

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 <vector>
    #include <queue>
    #include <functional> // greater算法的头文件
    
    void TestPriorityQueue()
    
        // 默认情况下,创建的是大堆,其底层按照小于号比较
        vector<int> v3,2,7,6,0,4,1,9,8,5;
        priority_queue<int> q1;
        for (auto& e : v)
        q1.push(e);
    
        cout << q1.top() << endl;
    
        // 如果要创建小堆,将第三个模板参数换成greater比较方式
        priority_queue<int, vector<int>, greater<int>> q2(v.begin(), v.end());
        cout << q2.top() << endl;
    
    
  2. 如果在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<Date> 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<Date, vector<Date>, greater<Date>> 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<int>& nums, int k) 
    
        // 将数组中的元素先放入优先级队列中
        priority_queue<int> p(nums.begin(), nums.end());
        
        // 将优先级队列中前k-1个元素删除掉
        for(int i= 0; i < k-1; ++i)
        
            p.pop();
        
        return p.top();
    
;

复杂度:时间:O(N*logN),空间:O(N)

解法二:用算法库中的sort排序,再取出倒数第k个元素

解法三:类似Topk问题,先将k个元素放入优先级队列(小堆),再遍历数组,如果比优先队列堆顶的数据大,就先pop堆顶数据,再push这个元素进队列。当遍历完后,优先级队列的首元素就是第K大的元素

class Solution 
public:
    int findKthLargest(vector<int>& nums, int k) 
        //将nums数组的前k个元素先放入优先队列(设置为小堆)
        priority_queue<int, vector<int>, greater<int>> pq(nums.begin(), nums.begin() + k);

        int i = k;
        while (i < nums.size())
        
            //比top大就入队
            if (nums[i] > pq.top())
            
                pq.pop();
                pq.push(nums[i]);
            

            ++i;
        

        return pq.top();
    
;

priority_queue的模拟实现

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

#include<iostream>
#include<vector>
#include<queue>
#include<functional>

using namespace std;

namespace ysj

	//仿函数
	template<class T>
	struct

以上是关于C++stack和queue及适配器的学习使用的主要内容,如果未能解决你的问题,请参考以下文章

C++stack与queue模拟实现

STL—queue和stack使用及源码剖析

STL详解—— stack和queue的介绍及使用

C++初阶:STL —— stack and queuestack/queue的介绍及使用 | stack/queue/priority_queue的深度剖析及模拟实现 | 适配器模式 | 仿函数

C++初阶:STL —— stack and queuestack/queue的介绍及使用 | stack/queue/priority_queue的深度剖析及模拟实现 | 适配器模式 | 仿函数

C++初阶:STL —— stack and queuestack/queue的介绍及使用 | stack/queue/priority_queue的深度剖析及模拟实现 | 适配器模式 | 仿函数