大话数据结构4之栈与队列
1. 栈是限定仅在表尾进行插入和删除操作的线性表。
队列是只允许在一端进行插入操作、而在另一端进行删除操作的线性表。
2.我们把允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为后进先出的线性表,简称LIFO结构。
栈的插入操作,叫做进栈,也称压栈、入栈。push
栈的删除操作,也叫出栈,也有的叫作弹栈。pop
3.栈的顺序存储结构
进栈操作push
出栈操作pop
时间复杂度均为O(1)
4.两栈共享空间
栈的顺序存储只准栈顶进出元素,不存在线性表插入和删除操作需要移动元素的问题,不过存在缺陷,就是必须事先确定数组存储空间的大小,万一不够用了,就需要编程手段来扩展数组的容量。
针对两个具有相同数据类型的栈的一个设计上的技巧,使用该数据结构,通常都是当两个栈的空间需求有相反关系时,也就是一个栈增长时另一个栈在缩短的情况。
用一个栈存储两个数组,关键思路是在数组两端向中间靠拢。
5.栈的链式存储结构(简称链栈)
栈顶放在单链表的头部,不需要头指针(变成栈顶指针)和头结点。
对于链栈来说,基本不存在栈满的情况,除非内存已经没有可以使用的空间。
对于空栈来说,链表原定以是头指针指向空,那么链栈的空其实就top=NULL的时候。
进栈push出栈pop,时间复度均为O(1)
如果栈的使用过程中元素的变化不可预料,有时很大有时很小,最好使用链栈,反之,如果他的变化在可控范围内,建议使用顺序栈会更好一些。
6.栈的作用
栈的引入简化了程序设计的问题,划分了不同关注层次,使得思考范围缩小,更加聚焦于我们要解决的问题核心。所以现在的许多高级语言java c#等都有对栈结构等封装,可以不用关注他的实现细节,可直接使用stack的push和pop方法,非常方便。
7.栈的应用--递归(斐波那契的递归函数)
把一个直接调用自己或通过一系列的调用语句间接的调用自己的函数,称作递归函数。
每个递归定义必须至少有一个条件,满足时递归不再进行,即不再引用自身而是返回值退出。
迭代和递归的区别是:迭代使用的是循环结构,递归使用的是选择结构。
在前行阶段,对于每一层递归,函数的局部变量、参数值以及返回地址都被压入栈中。在退回阶段,位于栈顶的局部变量、参数值、以及返回地址被弹出,用于返回调用层次中执行代码的其余部分,也就是恢复了调用的状态。
8.栈的应用--四则运算表达式求值
一种不需要括号的后缀表达法,也称为逆波兰(reverse polish notation ,RPN)表示。
9+(3-1) *3+10/2(标准四则运算表达式,中缀表达式)。 后缀表达式为: 9 3 1 -3 * +10 2 / +
叫后缀的原因在于所有的符号都是在要运算数字的后面出现。
运算规则:从左到右遍历表达式的每个数字和符号,遇到是数字就进栈,遇到是符号就将处于栈顶两个数字出栈,进行运算,运算结果进栈,一直到最终获得结果。
中缀表达式转化为后缀表达式规则:--
要让计算机具有处理我们通常标准(中缀)表达式的能力,最重要的两步:
1.将中缀表达式转化为后缀表达式(栈用来进出运算的符号)
2.将后缀表达式进行运算得出结果(栈用来进出运算的数字)
9.队列
队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。
队列是一种先进先出的线性表,简称FIFO.允许插入的一端称为队尾,允许删除的一端称为队头。
10.循环队列
队列的顺序存储结构,为避免当只有一个元素时,队头和队尾重合使处理变得麻烦,所以引入两个指针,front指针指向队头元素,rear指针指向队尾元素的下一个位置,这样当front等于rear 时,此队列不是还剩下一个元素,而是空队列。会出现“假溢出”的现象
解决假溢出的办法就是后面满了,就再重头开始,也就是头尾相接的循环。我们把队列的这种头尾相接的顺序存储结构称为循环队列。
单是顺序存储,若不是循环队列,算法的时间性能是不高的,但循环队列又面临着数组可能会溢出的问题。所以需要研究不需要担心队列长度的链式存储结构。
11.队列的链式存储结构:就是线性表的单链表,不过它只能尾进头出,简称为链队列。
在队列长度最大值的情况下,建议使用循环队列,如果你无法预估队列的长度时,则用链队列。
栈 队列
顺序栈 顺序队列
两栈共享空间 循环队列
链栈 链队列