栈的定义
栈(stack):是限定仅在表尾进行插入和删除操作的线性表。
- 允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。
- 栈又称为后进先出(Last In First Out)的线性表,简称LIFO结构。
栈的插入操作:叫作进栈,也称压栈、入栈。
栈的删除操作:叫作出栈,也有的叫作弹栈。
栈的抽象数据类型
ADT栈(stack)
Data
同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。
Operation
InitStack(*s):初始化操作,建立一个空栈S。
DestroyStack(*s):若栈存在,则销毁它。
ClearStack(*s):将栈清空。
StackEmpty(s):若为空,返回true,否则返回false。
GetTop(S,*e):若栈存在且非空,用e返回s的栈顶元素。
Push(*s,e):若栈S存在,插入新元素e到栈S中并成为栈顶元素。
Pop(*S,*e):删除栈s中顶元素,并用e返回其值。
StackLength(s):返回栈s的元素个数。
endADT
栈的顺序存储结构
进栈操作、出栈操作没有涉及到任何循环语句,因此时间复杂度均是0(1)。
两栈共享空间
实现方法:数组有两个端点,两个栈有两个栈底,让一个栈的栈底为数组的始端,即下标为0处,另一个栈为栈的末端,即下标为数组长度n-1处。这样,两个栈如果增加元素,就是两端点向中间延伸。
关键思路:它们是在数组的两端,向中间靠拢,top1和top2是栈1和栈2的栈顶指针。
- top1等于-1时栈1为空,top2等于n时栈2为空。
- 两个栈见面之时,也就是两个指针之间相差1时,即top1+1==top2为栈满。
应用场景:这只是针对两个具有相同数据类型的栈的一个设计上的技巧,使用这样的数据结构通常都是当两个栈的空间需求有相反关系时,也就是一个栈增长时另一个栈在缩短的情况。
栈的链式存储结构及实现
栈的链式存储结构,简称为链栈。
栈的链式存储结构——进栈操作:对于链栈的进栈push操作,假设元素值为e的新结点是s,top为栈顶指针,如下图所示
栈的链式存储结构——出栈操作:至于链栈的出栈pop操作,假设变量p用来存储要删除的栈顶结点,将栈顶指针下移一位,最后释放p即可,如下图所示
链栈的进栈push和出栈pop操作都很简单,没有任何循环操作,时间复杂度均为O(1)
使用建议:如果栈的使用过程中元素变化不可预料,有时很小,有时非常大,那么最好是用链栈,反之,如果它的变化在可控范围内,建议使用顺序栈会更好一些。
栈的作用
栈的引入简化了程序设计的问题,划分了不同关注层次,使得思考范围缩小,更加聚焦于我们要解决的问题核心,不用像数组一样因为要分散精力去考虑数组的下标增减等细节问题掩盖了问题的本质。
现在的许多高级语言都有对栈结构的封装,可以直接使用Stack的push和pop方法。
栈的应用——递归
斐波那契数列:
递归定义:把一个直接调用自己或通过一系列的调用语句间接地调用自己的函数,称做递归函数。每个递归定义必须至少有一个条件,满足时递归不再进行,即不再引用自身而是返回值退出。
编译器使用栈实现递归:在前行阶段,对于每一层递归,函数的局部变量、参数值以及速回地址都被压入栈中。在退回阶段,位于栈顶的局部变量、参数值和返回地址被弹出,用于返回调用层次中执行代码的其余部分,也就是恢复了调用的状态。
栈的应用——四则运算表达式求值
中缀表达式(标准表达式):9+(3-1)*3+10/2
后缀表达式:9 3 1-3*+ 10 2/+
处理标准(中缀)表达式:
- 将中缀表达式转化为后缀表达式(栈用来进出运算的符号)—将算术表达式转化为后缀表达式
- 将后缀表达式进行运算得出结果(栈用来进出运算的数字)—后缀表达式的计算
队列的定义
队列(queue):是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
- 队列是一种先进先出(First In First Out)的线性表,简称FIFO。
- 允许插入的一端称为队尾,允许删除的一端称为队头。
队列的抽象数据类型
ADT 队列(Queue)
Data
同线性表。元素具有相同的类型,相邻元素具有前驱和后继关系。
Operation InitQueue(*Q):初始化操作,建立一个空队列Q。
DestroyQueue(*Q):若队列Q存在,则销毁它。
ClearQueue(*Q):将队列Q清空。
QueueEmpty(Q):若队列Q为空,返回true,否则返回false。
GetHead(Q,*e):若队列Q存在且非空,用e返回队列Q的队头元素。
EnQueue(*Q,e):若队列Q存在,插入新元素e到队列Q中并成为队尾元素。
DeQueue(*Q,*e):删除队列Q中队头元素,并用e返回其值。
QueueLength(Q):返回队列Q的元素个数
endADT
循环队列
循环队列面临数组可能溢出的问题。
队列顺序存储的不足:数组越界、假溢出、时间性能不高
循环队列定义:把队列的这种头尾相接的顺序存储结构称为循环队列。
队列的链式存储结构及实现
队列的链式存储结构:其实就是线性表的单链表,只不过它只能尾进头出而已,我们把它简称为链队列。为了操作上的方便将队头指针指向链队列的头结点,而队尾指针指向终端结点:
空栈时:
队列的链式存储结构——入队操作:在链表尾部插入结点。
队列的链式存储结构——出队操作:头结点的后继结点出队,将头结点的后继改为它后面的结点,若链表除头结点外只剩一个元素时,则需将rear指向头结点。
注:在可以确定队列长度最大值的情况下,建议用循环队列,如果无法预估队列的长度时,则用链队列。