栈和队列

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了栈和队列相关的知识,希望对你有一定的参考价值。

栈:

特点:先进先出
成员函数
stack()--构造
empty()--判空
size() --求个数
top() --返回栈顶元素
push --压栈
pop() --出栈

应用

  • 最小栈创建:(可直接返回栈中最小值,即在该栈中查找最小值复杂度为O(1))

因为栈中最小值是随push和pop操作变化的,进栈时min会更新,出栈min也可能会更新,所以每个元素进栈时当前栈中最小值应该被保存下来

//  法一:
class MinStack{public :
MinStack() {

}

public void push(int x) {
    int tmp = stack.top();
    if(stack.isEmpty()||tmp>x){
        stack.push(x);
        stack.push(x);
    }
        else{
       stack.push(x);
        stack.push(tmp);
    }
}

public void pop() {
    s.pop();
    s.pop();
}

public int top() {
    return s.get(s.size()-2);
}

public int getMin() {
    return s.top();
}
private:
 stack<int> s;
}
//法二:在最小栈中封装两个栈,一个存元素,一个存最小值。
//       push:   当存入当前元素的值是目前栈中最小值或等于最小值时,就存进去
//       pop:     当出来的值等于存最小值栈中的栈顶元素时,两个一起pop出,否则只pop存元素的
class MinStack {
public:
    MinStack() 
    {  
    }
    void push(int x) {
        svalue.push(x);
        if(smin.empty()||x<=smin.top())
        {   
            smin.push(x);
        }

    }

    void pop() {

        if(!svalue.empty())
        {
            if(svalue.top()==smin.top())
            {
                smin.pop();
            }
            svalue.pop();
        } 
    }

    int top() {   
        return svalue.top();
    }

    int getMin() {
        return smin.top();
    }
    private:
    stack<int> svalue;
    stack<int> smin;
};
  • 判断出栈顺序正不正确
class Solution {
public:
    bool IsPopOrder(vector<int> pushV,vector<int> popV) {
        if(pushV.size()!=popV.size())
        {
            return false;
        }
        int index=0;
        int outdex=0;
        stack<int> s;
        while(outdex<popV.size())
        {
            while(s.empty()||s.top()!=popV[outdex])   //s.empty()  因为栈为空的时候s.top()非法空间不可引用
            {
                if(index>=pushV.size())
                {
                    return false;
                }
                s.push(pushV[index]);
                index++;
            }
            outdex++;
            s.pop();
        }
        return true;
    }
};
  • 逆波兰表达式求值

    class Solution {
    public:
    int evalRPN(vector<string>& tokens) {
         stack<int> s;
        int index=0;
        int x,y;
        if(tokens.size()==1)
        {
            return atoi(tokens[0].c_str());
        }
        while(index<tokens.size())
        {
    
            while(tokens[index]!="+"&&tokens[index]!="-"&&tokens[index]!="*"&&tokens[index]!="/")
            {
                s.push(atoi(tokens[index++].c_str()));            
            }
            y = s.top();
            s.pop();
            x = s.top();
            s.pop();
            switch(tokens[index++].c_str()[0])
            {
                case ‘+‘:s.push(x+y);break;
                case ‘-‘:s.push(x-y);break;
                case ‘*‘:s.push(x*y);break;
                case ‘/‘:{
                    if(y!=0)
                    s.push(x/y);
                    else
                        s.push(0);
                    break;
                }
    
            }      
        }
        return s.top();
    }
    };

在树的遍历中,也会用到栈,一定要练题!

队列

特点:先进后出
成员函数
queue()--构造
empty()--判空
size() --求个数
front()--返回队头元素
back() --返回队尾元素
push --压栈
pop() --出栈

在此介绍一种很重要的队列叫做优先队列

优先队列

按照堆的顺序存储元素,默认是通过vector来存储的
他所用的函数模板是:

template <class T,class Container=vector<T>,class Compare=less<T>)

说明:1.在默认的情况下,创建的是大堆顺序存储,用的是less方法的比较
创建对象时,可通过传greater对象来创建一个小堆的顺序

//例:
priority_queue<int>  q1;
priority_queue<int,vector<int>,less<int>>  q2;
//q1和q2创建出来都是以vector容器,大根堆顺序 存储

priority_queue<int,deque<int>,greater<int>> q3;
//q3创建出来是以deque容器,小根堆顺序 存储
//根据形参赋值规则,要从头开始赋值,所以要串时参时,传比较方法一定要带上承载容器
    vector<int> v{ 2, 5, 3, 8, 9, 0, 1 };
    priority_queue<int> q3(v.begin(), v.end());  

2.如果在优先级队列中放自定义数据类型,用户需要在自定义类型中提供>或 < 的重载

//例:
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);
    }
    /*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;
};
class  Less
{
public:
    bool operator()(const Date* pLeft, const Date* pRight){
        return *pLeft < *pRight;

    }
};

void Test(){
    Date d1(2019, 10, 21);
    Date d2(2019, 10, 20);
    Date d3(2019, 10, 22);

    priority_queue<Date> q1;
  q1.push(d1);
    q1.push(d2);
    q1.push(d3);

//对于指针,如果不提供比较方法,会用地址大小来使用堆排序
//如果想按照指针所指向空间里存放的元素来比较,也要自己提供比较方式
//因而,要按照自己的需求排序时,都要自己提供比较方式才能达到目的
//如下文中的Less方法
    priority_queue<Date*, vector<Date*>, Less> q2;    
    q2.push(&d1);
    q2.push(&d2);
    q2.push(&d3);
}

提出一个重要概念:

容器适配器:

适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结),该中模式是将一个类的接口转换成客户希望的另外一个接口。
在类中具体实现方式:一个类在底层通过将其他容器进行封装,通过提供不同的函数接口,以达到不同的功能。
stack、queue、priority_queue 都是容器适配器,因为在STL中,每个容器都有自己的实现方式,而他们只是通过对其他容器进行封装而成,因为是容器适配器,而并不是容器

stack、queue、priority_queue底层的大概实现方式

namespace MyStack{
    template<class T,class Container=deque<T>>  //默认用栈承载
    class stack{
    public:
        stack(){

        }
        void push(const T &value){
            q.push_back(value);
        }
        void pop(){
            q.pop_back();
        }
        T& top(){
            return q.back();
        }
        const T& top() const
        {
            return q.back();
        }
        size_t size(){
            return q.size();
        }
        bool empty(){
            return q.empty();
        }
    private:
        Container  q;
    };
}

void Test2()
{
    MyStack::stack<int,list<int>> s1;
    s1.push(1);
    s1.push(2);
    s1.push(3);
    s1.pop();
    cout << s1.size() << endl;
    cout << s1.empty() << endl;
}
namespace MyQueue{
    template<class T, class Container = deque<T>>
    class queue{
    public:
        queue(){

        }
        void push(const T &value){
            q.push_back(value);
        }
        void pop(){
            q.pop_front();
        }

        T& front(){
            return q.front();
        }
        const T& front() const
        {
            return q.front();
        }
        T& back() 
        {
            return q.back();
        }
        const T& back() const
        {
            return q.back();
        }

        size_t size(){
            return q.size();
        }

        bool empty(){
            return q.empty();
        }
    private:
        Container  q;     、
        //用的是一个其他容器,在该类中提共其他方法在进行封装就成了栈
    };
}

void Test3()
{
    MyQueue::queue<int,list<int>> q1;
    q1.push(1);
    q1.push(2);
    q1.push(3);
    q1.pop();

    cout << q1.size() << endl;
    cout << q1.empty() << endl;
}

为什么stack和queue使用的是deque而不是vector?
因为对于deque来说,扩容是很容易的,只是将指针改变一下,而vercot又得重新将元素搬来搬去效率很低。此外,栈和队列不会去遍历(不提供迭代器),这个特性又把deque的弊端给忽略了。
不用list是因为list的空间利用率不高

namespace MyPriorityqueue{
    template <class T,class Contain=vector<T>,class Compare=less<T>>
    class priority_queue{
    public:

        priority_queue()
            :_con()
        {

        }
        template <class Iterator>
        priority_queue(Iterator start, Iterator end)
            :_con(start, end)
        {

            for (int i =( _con.size()-2)/2; i >=0; i--)
            {
                AdjustDown(i);
            }
        }
        void size(){
            return _con.size();
        }
        bool empty(){
            return _con.empty();
        }
        void push(const T& value){
            _con.push_back(value);
            AdjustUP(_con.size() - 1);

        }
        void pop(){
            swap(_con.front(), _con.back());
            _con.pop_back();
            AdjustDown(0);

        }

    private:
        void AdjustDown(int parent){
            int child = parent * 2 + 1;
            while (child<_con.size())
            {
                if (child+1<_con.size()&&_comp(_con[child], _con[child + 1]))
                {
                    child += 1;
                }
                if (_comp(_con[parent], _con[child]))
                {
                    swap(_con[parent], _con[child]);
                    parent = child;
                    child = parent * 2 + 1;
                }
                else
                    return;

            }
        }
        void AdjustUP(int child)
        {
            int parent = (child - 1) / 2;
            while (child>0){
                if (_comp(_con[parent],_con[child]))
                {
                    swap(_con[parent], _con[child]);
                    child = parent;
                    parent = (child - 1) / 2;           
                }
                else{
                    return;
                }
            }
        }
    private:
        Contain _con;
        Compare _comp;
    };

}

void Test4(){
    deque<int> dq{ 1, 2, 3 ,5,6,2};
    MyPriorityqueue::priority_queue<int,deque<int>,greater<int>> p1(dq.begin(),dq.end());
    p1.pop();
    p1.push(0);

}

以上是关于栈和队列的主要内容,如果未能解决你的问题,请参考以下文章

栈和队列基本操作

栈和队列

博客作业03--栈和队列

博客作业03--栈和队列

栈和队列知识点总结

栈和队列的面试题Java实现