数据结构之栈

来源:互联网 发布:mysql like 编辑:程序博客网 时间:2024/06/10 03:12

一、栈(Stack)的结构

数据先进先出(first in last out)如图所示:



例如日常生活中我们把盘子摞起来,最先放置的盘子,最后才能取出。

二、栈操作实例

如下图所示:
● Stack()构造函数创建一个栈
● empty()检查这个栈是否为空(true or false)
● push(5)往栈里插入元素5
● pop()删除顶层元素x,并且x作为返回元素交给上层的使用者
● top()返回顶层元素y
● size()返回元素的个数


三、栈的实现

栈既然输入序列的特例,故可直接基于列表或向量派生,将向量的末端作为栈顶时,时间复杂度为O(1)。
Note:需要特别说明,我们也可以将向量的顶端作为栈顶,但是插入和删除操作都会涉及到向量中的所有元素,则入栈和出栈的时间复杂度为O(n)



C++中有Stack模版类:

s.empty()               //如果栈为空返回true,否则返回false  s.size()                //返回栈中元素的个数  s.pop()                 //删除栈顶元素但不返回其值  s.top()                 //返回栈顶的元素,但不删除该元素  s.push()                //在栈顶压入新元素  


四、栈的应用

(1)逆序输出问题(conversion)

输出次序和实际输出颠倒
递归深度和输出长度不易预知

EX:进制转换问题

按进制转换的定义,整除法算出的结果逆序输出,我们可以把整除的结果依次入栈,再出栈,则得到最终结果。

(2)递归嵌套(stack permtation + parenthesis)

具有自相似性的问题可递归描述,但分支位置和嵌套深度不固定

EX1:括号匹配问题,leetcode No20

bool isValid(string s) {          stack<char> res;                                  //使用栈记录已发现但尚未匹配的左括号        for(int i=0;i<s.size();i++)                       //逐一检查当前字符        {              if(s[i]=='('||s[i]=='{'||s[i]=='[')           //遇到左括号,进栈                res.push(s[i]);              else if(s[i]==')'||s[i]=='}'||s[i]==']')      //遇右括号            {                  if(res.empty()==1)                        //若栈此时为空,返回false                         return false;                  if((res.top()=='('&&s[i]==')')||(res.top()=='{'&&s[i]=='}')||(res.top()=='['&&s[i]==']'))                      res.pop();                            //若栈顶元素和当前元素匹配,则出栈                else                       return false;              }          }          return res.empty(); 

EX2:栈混洗

定义:栈混洗就是根据某种规则,对栈中的元素进行重新排列
Note:用<表示栈顶,]表示栈底
初始情况:A=<a1,a2,...,an]
B=S=Φ
规则:只允许将A的顶元素弹出并压入S,或将S的顶元素弹出并压入B
 若经过一系列以上操作后,A中元素全部转入B中
结束情况:B=[ak1,ak2,...,akn>
则称之为A的一个栈混洗(stack permutation),输入同一序列,可能有多种栈混洗。
对于长度为n的序列,可能的栈混洗总数SP(n)=catalan(n)=(2n)!/((n+1)!n!)。
甄别:那么我们如何甄别一个排列是不是栈混洗,先说一个简单的情况<1,2,3],n=3,SP(3)=5,而3的全排列个数为6,所以只有一种情况是非法的,这个情况就是[3,1,2>,因为3是需要第一个进栈,那么为了让3第一个进栈,那么2,1必须先进S,那么3后面只能是2。
一般情况下,在一个栈中,对于任何1小于等于i<j<k小于等于n,[...,k,...,i....,j,...>则必非栈混洗。(充要条件)
A permutation is a stack permutation iff it does NOT involve the permutation(312)
Algorithm:按照甄别的充要条件我们可以写出O(n^3)的甄别算法(Too Slow)
我们还可以得到一个改进的性质,即[p1,p2,...,pn>是<1,2,...,n]的栈混洗,当且进当对于任意的i<j不含模式[ ...,j+1, ..., i , ... , ... >这样我们可以得到一个O(n^2)的算法。
O(n)算法,直接借助栈A、B和S,模拟混洗过程,每次S.pop()之前,检测S是否为空,或需弹出的元素在S中,却非顶元素。即如果我们直接模拟这样一种栈混洗的过程,建立三个栈,先后完成push()...pop()...push()的操作,保证能依次得到目标序列,那么就可以确定这样的序列是可以得到的,反之就不能得到,这样的时间度最小为O(n)

(3)延迟缓冲(evaluation)

线性扫描算法模式中,在预读够长之后,方能确定可处理的前缀
EX:中缀表达式(infex)求值
Question:  给定语法正确的算数表达式S,计算与之对应的数值
Algorithm:使用两个栈,一个栈存放运算符,一个栈存放操作数,还需要一个运算符的优先级表
具体实例可以参照学堂在线的数据结构(邓俊辉)栈这一节,有非常详细的讲解过程

(4)栈式计算

RPN:逆波兰表达式(Reverse Polish Notation)

基于栈结构的特定计算机模式

中缀表达式(infix):由运算符(operator)和操作数(operand)组成的表达式中不使用括号(parenthesis-free),即可表示带优先级的运算关系

缺点:定义混乱,逻辑复杂
RPN:哪个运算符先出现就先计算
举个例子:0!1+23!4-5^-67*-8-9+
只需要一个辅助栈
infix-postfix的转化,目标是消除所有的括号
例如:(0!+1)^(2*3!+4-5)
假设:事先未就运算符之间的优先级关系做过任何约定
1)用括号显式地表示优先级
2)将运算符移到对应的右括号后
3)抹去所有括号
4)稍事整理,即得逆波兰表达式
Note:运算符的相对位置会发生变化,但是操作数的相对位置不会变化
Algorithm:中缀表达式求值过程其实就把RPN求解出来了



0 0
原创粉丝点击