【概述】
栈(Stack)是一种特殊的线性表,只能在某一端插入和删除的特殊线性表。它按照后进先出的原则存储数据,先进入的数据被压入栈底,最后的数据在栈顶。
由于栈满足先进后出,后进先出的性质,因此也被称为先进后出表(FILO)或后进先出表(LIFO)
当栈中元素个数为零时称为空栈。
【栈的逻辑结构】
栈的栈底固定,栈顶浮动,允许进行插入和删除操作的一端称为栈顶(Top),另一端为栈底(Bottom),插入一个元素称为进栈(Push),删除一个栈顶元素称为出栈(Pop)。
无论是顺序栈还是链栈,其出栈、入栈的时间复杂度均为 O(1)
当栈满时再插入元素,将发生上溢,当栈为空时删除元素,将发生下溢。
【栈的顺序存储结构】
1.顺序栈
栈的顺序存储结构即顺序栈,其使用数组来模拟栈,并设置一个栈顶指针 top,选用 a[0] 作为栈底,a[top] 作为栈顶
其元素个数存在限制,并且可能造成空间浪费。
2.双端栈
在一个程序中有时需要同时使用具有相同数据类型的两个栈,当为他们开辟相同的存储空间后,某一时刻一个栈已经满了,而另一个栈还有很大的空间,这样会造成存储空间的浪费,此时可以令两栈共享空间,即使用双端栈。
使用一个数组来存储两个栈,让一个栈的栈底为该数组的始端(a[0]),另一个栈的栈底为该数组的末端(a[n-1]),两个栈从各自的端点向中间延伸(top1++,top2--)。
由于双端栈的特性,因此其一般是用于具有两栈空间需求存在相反情况时,即一个栈增长,另一个栈缩短的情况。
【栈的逻辑存储结构】
栈的逻辑存储结构即链栈,其将链表的头指针作为栈顶,便于插入、删除操作。
由于其空间是动态扩展的,因此一般不存在上溢的问题,只有当内存没有可用空间时才会出现栈满,但每个元素都需要一个指针域,从而产生了结构性开销。
【应用】
1.中缀表达式
中缀表达式:运算符在两个运算对象中间,基本运算符有 +、-、*、/、()、# 等,其中 # 为中缀表达式的界定符
例如:#3*(4+2)/2-5# 就是一个中缀表达式
在进行运算时,要考虑运算符的优先级与结合性,常见运算符优先级表如下:
中缀表达式的求值过程用到两个栈:运算对象栈 Opnd、运算符栈 Optr,算法伪代码如下:
- Opnd 初始化为空,Optr 初始化为表达式的界定符 #
- 从左到右扫描表达式的每一个字符
1)若当前字符是操作数:入栈 Opnd
2)若当前字符是运算符
①当前运算符优先级 > 栈 Optr 的栈顶运算符优先级:入栈 Optr,处理下一字符
②当前运算符优先级 < 栈 Optr 的栈顶运算符优先级:栈 Opnd 出栈两个操作数,栈 Optr 出栈一个运算符,进行运算后将结果入栈 Opnd,处理当前字符
③当前运算符优先级 = 栈 Optr 的栈顶运算符优先级:栈 Optr 栈顶元素出栈,处理下一字符- 当栈 Optr 为空时,输出栈 Opnd 栈顶元素,即为表达式运算结果
以下图为例:
2.中缀表达式转后缀表达式
后缀表达式:所有的计算按照运算符出现的顺序从左向右进行,不用考虑运算符的优先级
例如:中缀表达式 3*(4+2)/2-5 对应后缀表达式 3 4 2 + * 2 / 5 -
为了处理方便,常将中缀表达式转为等价的后缀表达式,在转换过程中用到一个栈 S,算法伪代码如下:
- 初始化栈 S
- 从左到右依次扫描中缀表达式的每一个字符
1)若当前字符是操作数:输出该字符,处理下一字符
2)若当前字符是运算符:
①当前运算符优先级 > 栈 S 栈顶运算符优先级:该运算符入栈 S,处理下一字符
②当前运算符优先级 < 栈 S 栈顶运算符优先级:栈 S 栈顶运算符弹出并输出,处理当前字符
③当前运算符优先级 = 栈 S 栈顶运算符优先级:栈 S 栈顶运算符弹出,处理向下一字符
3.后缀表达式求值
后缀表达式的求值过程中用到一个栈 S,算法伪代码如下:
- 初始化栈 S
- 从左到右依次扫描表达式的每一个字符
1)若当前字符是操作数:入栈 S,处理下一个字符
2)若当前字符是运算符:栈 S 出栈两个操作数,进行运算并将执行结果入栈 S,处理下一个字符- 当表达式的所有字符都处理完成,输出栈顶元素,即为表达式运算结果
以下图为例: