栈和队列

Posted 乘风有时

tags:

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

栈和队列都属于限制了插入、删除操作的表。栈要求插入、删除操作都只能作用在一端;队列要求插入、删除操作不能作用在同一端。

因此,栈是先进后出的一种数据结构,队列是先进先出的数据结构。

C++的标准库中有栈模板,它的详细操作介绍请参考这篇博文:http://www.cnblogs.com/yeqluofwupheng/p/6711265.html

下面从算法和应用等方面介绍栈。

顺序栈和链栈

栈的实现可以是建立在数组或链表上的,建立在数组上的顺序栈和建立在链表上的链栈也有一些区别:

  • 在顺序栈中,定义了栈的栈底指针(存储空间首地址base)、栈顶指针top以及顺序存储空间的大小stacksize;而对于链栈来说,它只定义栈顶指针。
  • 顺序栈和链栈的top指针有区别,顺序栈的top指针指向栈定的空元素处,top-1才指向栈顶元素,而链栈的top指针相当于链表的head指针一样,指向实实在在的元素。
  • 在空间上,顺序表是静态分配的,而链表则是动态分配的;就存储密度来说:顺序表等于1,而链式表小于1。
  • 顺序栈由于有栈空间大小,所以一般栈中的空间是有限制的,而链栈是动态分配,它的空间大小等于可用的整个内存空间。

链栈的简单实现:

class LinkStack{
public:
    bool isEmpty(){ return !head; }
    void push(int val){
        ListNode *r = new ListNode(val);
        r->next = head;
        head = r;
    }
    void pop(){
        if (isEmpty())return;
        ListNode *p = head;
        head = head->next;
    }
    int top(){
        if (isEmpty())return -1;//抛出异常
        return head->val;
    }
private:
    ListNode *head = nullptr;
};

Min Stack

LeetCode中有一道题,设计最小栈Min Stack。支持push、top、pop、getMin这四种操作。

  • push(x) -- 将x入栈.
  • pop() -- 栈顶元素出栈.
  • top() -- 得到栈顶元素的值.
  • getMin() -- 返回栈中最小值的元素.

使用双栈来实现最小栈

class MinStack {
public:
    /** initialize your data structure here. */
    MinStack() {

    }

    void push(int x) {
        if (s.empty() || s.top().second > x){//需要更新最小值
            s.push(make_pair(x, x));
        }
        else{//上一个的最小值是当前的最小值
            s.push(make_pair(x, s.top().second));
        }
    }

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

    int top() {
        return s.top().first;
    }

    int getMin() {
        return s.top().second;
    }
private:
    stack<pair<int,int>>s;//first存当前位置的实际值,second存当前位置的最小值
};

Implement Stack using Queues

使用队列来实现栈,要求包含下面的操作:

  • push(x) -- 将x入栈.
  • pop() -- 栈顶元素出栈.
  • top() -- 得到栈顶元素的值.
  • empty() -- 判断栈是否为空.
class MyStack {
public:
    /** Initialize your data structure here. */
    MyStack() {

    }

    /** Push element x onto stack. */
    void push(int x) {
        Q.push(x);
        for (size_t i = 1; i < Q.size(); i++){
            Q.push(Q.front());
            Q.pop();
        }
    }

    /** Removes the element on top of the stack and returns that element. */
    int pop() {
        int v = Q.front();
        Q.pop();
        return v;
    }

    /** Get the top element. */
    int top() {
        return Q.front();
    }

    /** Returns whether the stack is empty. */
    bool empty() {
        return Q.empty();
    }
private:
    queue<int>Q;
};

栈的应用

数制转换:即将十进制转换为K进制;

括号匹配:{}、()、[]三种括号的嵌套和匹配。

表达式求值:给定一个算数表达式字符串,求出它的值。

中缀与后缀表达式的转换:将一个中缀表达式转换成后缀表达式。

队列

Implement Queue using Stacks

使用栈实现队列,要求包含下面操作:

  • push(x) -- 将x入队.
  • pop() -- 队列的头部的元素出队.
  • peek() -- 查看队列的头部的元素.
  • empty() -- 返回队列是否为空.

双栈实现队列。

class MyQueue {
public:
    /** Initialize your data structure here. */
    MyQueue() {

    }

    /** Push element x to the back of queue. */
    void push(int x) {
        sin.push(x);
    }

    /** Removes the element from in front of queue and returns that element. */
    int pop() {
        int v = 0;
        if (sout.empty()){
            while (!sin.empty()){
                sout.push(sin.top());
                sin.pop();
            }
        }
        v = sout.top();
        sout.pop();
        return v;
    }

    /** Get the front element. */
    int peek() {
        if (sout.empty()){
            while (!sin.empty()){
                sout.push(sin.top());
                sin.pop();
            }
        }
        return sout.top();
    }

    /** Returns whether the queue is empty. */
    bool empty() {
        return sin.empty() && sout.empty();
    }
private:
    stack<int>sin,sout;
};

队列的应用

例如,打印机中的任务队列,售票时每个窗口的队列,系统中的消息队列等,很多场景需要用到队列这一结构。

双端队列

双端队列对应C++标准库中的deque容器,双端队列是一种具有队列和栈的性质的数据结构。双端队列中的元素可以从两端弹出,其限定插入和删除操作在表的两端进行。

deque采用分块的线性存储结构来存储数据,每块的大小一般为512B,将之称为deque块,所有的deque块使用一个map块进行管理,每个map数据项记录各个deque块的首地址。这样的话,deque块在头部和尾部都可以插入和删除,而不需要移动其他元素(使用

push_back()方法在尾部插入元素,会扩张队列,而使用push_front()方法在首部插入元素和使用insert()方法在中间插入元素,只是将原位置上的元素进行覆盖,不会增加新元素)一般来说,当考虑到容器元素的内存分配策略和操作的性能时deque相当于vector更有优势。

deque的构造方法

  • deque<type> deq                                        创建一个没有任何元素的双端队列
  • deque<type> deq(otherDeq)                    用另一个类型相同双端队列初始化该双端队列
  • deque<type> deq(size)                              初始化一个固定size的双端队列
  • deque<type> deq(n, element)                  初始化n个相同元素的双端队列
  • deque<type> deq(begin,end)                   初始化双端队列中的某一段元素,从begin 到 end - 1

deque的操作

  • deq.assign(n,elem)               赋值n个元素的拷贝给双端队列
  • deq.assign(beg,end)            赋值一段迭代器的值给双端队列
  • deq.push_front(elem)           添加一个元素在开头
  • deq.pop_front()                       删除第一个元素
  • deq.at(index)                           取固定位置的元素
  • deq[index]                                取固定位置的元素
  • deq.front()                                返回第一个元素(不检测容器是否为空)
  • deq.back()                                返回最后一个元素(不检测容器是否为空)

输入受限的双端队列

即:一端既能输入又能输出,另一端只能输出的双端队列;

输出受限的双端队列

即:一端既能输入又能输出,另一端只能输入的双端队列;

一个经典问题:

如果以1、2、3、4为一个双端队列的输入序列,则满足下面条件的输出序列是什么?

1)能够通过一个输入受限的双端队列得到,但不能通过输出受限的双端队列得到;

2)能够通过一个输出受限的双端队列得到,但不能通过输入受限的双端队列得到;

3)既不能通过一个输入受限的双端队列得到,又不能通过输出受限的双端队列得到;

输入受限的双端队列不可以得到:4213、4231

输出受限的双端队列不可以得到:4132、4231

因此上面的答案分别是

1)4132;2)4213;3)4231

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

栈和队列基本操作

栈和队列

博客作业03--栈和队列

博客作业03--栈和队列

栈和队列知识点总结

栈和队列的面试题Java实现