栈和队列都属于限制了插入、删除操作的表。栈要求插入、删除操作都只能作用在一端;队列要求插入、删除操作不能作用在同一端。
因此,栈是先进后出的一种数据结构,队列是先进先出的数据结构。
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