每日学习录(数据结构—栈和队列)

目录

4.1 栈

4.11 栈的定义

4.2 栈的抽象数据类型

4.3 站的顺序存储结构及实现

4.31 栈的顺序存储结构

4.32 栈的顺序存储结构——进栈操作

4.43 栈的顺序结构——出栈操作

4.4 两栈共享空间

4.5 栈的链式存储结构及实现

4.51 栈的链式存储结构(链栈)

4.52 栈的链式存储结构——进栈操作

4.53 栈的链式存储结构——出栈操作

4.6 栈的应用——四则运算表达式求值

4.61 后缀(逆波兰)表示法定义

4.62 后缀表达式计算结果

4.63 中缀表达式转后缀表达式

4.7 队列的定义

4.8 队列的抽象数据类型

4.9 循环队列

4.91 队列的顺序存储

 4.92 循环队列的定义

4.10 队列的链式存储结构及实现

4.10.1 队列的链式存储结构——入队操作

4.10.2 队列的链式存储结构——出队操作


4.1 栈

4.11 栈的定义

栈(stack)是限定仅在表尾进行插入和删除操作的线性表。

把允许插入和删除的一端尾栈顶(top),另一端为栈底(bottom),不含任何数据元素的栈称为空栈。栈又称为后进后出(Last In First Out)的线性表,简称LIFO。

首先它是一个线性表,也就是说,栈元素具有线性关系,即前驱后继关系。只不过它是一种特殊的线性表而已。定义中说是在线性表的表尾进行插入和删除操作,这里表尾是指栈顶,而不是栈底。

它的特殊之处就在于限制了这个线性表的插入和删除位置,它始终只在栈顶进行。这也就使得:栈底是固定的,最先进栈的只能在栈底。

栈的插入操作,叫作进栈,也称压栈、入栈。类似子弹入弹夹,如图4-2-2所示。

栈的删除操作,叫作出栈,也有的叫作弹栈。如同弹夹中的子弹出夹,如图4-2-3所示。

4.2 栈的抽象数据类型

对于栈来讲,理论上线性表的操作特性它都具备,可由于它的特殊性,所以针对它在操作上会有些变化。特别是插入和删除操作,我们改名为pushpop,英文直译的话是压和弹,更容易理解。你就把它当成是弹夹的子弹压入和弹出就好记忆了,我们一般叫进栈和出栈。

4.3 站的顺序存储结构及实现

4.31 栈的顺序存储结构

既然栈是线性表的特例,那么栈的顺序存储其实也是线性表顺序存储的简化,我们简称为顺序栈。线性表是用数组来实现的,想想看,对于栈这种只能一头插入删除的线性表来说,用数组哪一端来作为栈顶和栈底比较好?

对,没错,下标为0的一端作为栈底比较好,因为首元素都存在栈底,变化最小,所以让它作栈底。

若存储栈的长度为StackSize,则栈顶位置top必须小于StackSize。当栈存在一个元素时,top等于0,因此通常把空栈的判定条件定为top等于-1

来看栈的结构定义:

假设现有一个StackSize为5的栈,则普通情况、空栈和栈满的情况示意图如下:

4.32 栈的顺序存储结构——进栈操作

对于栈的插入,即进栈操作,示意图如下:

对于进栈操作push,其代码如下:

4.43 栈的顺序结构——出栈操作

出站操作pop,代码如下:

两者没有涉及到任何循环语句,因此时间复杂度均为O(1)。

4.4 两栈共享空间

如果有两个相同类型的栈,为它们各自开辟数组的,极有可能是第一个栈已经满了,再进栈就溢出了,而另一个栈还有很多存储空间空闲,造成了一定的浪费。我们可以有一个数组来存储两个栈,充分利用这个数组占用的内存空间。

实现的方法如下图,数组有两个端点,两个栈有两个栈底,让一个栈的栈底为数组的始端,另一个栈的栈底为数组的末端,即下标为数组长度n-1处。这样一来,栈如果增加元素,就是两端点向中间延伸。

关键思路:从数组两端向中间靠拢,只要top1和top2不相遇,两个栈就可以一直使用。

栈空的情况:栈1为空时,top1等于-1,栈2为空时,top2等于n;
栈满的情况:想想极端的情况,若栈2为空时,栈1的top1等于n-1时,栈1满。反之当栈1为空时,栈2的top2等于0时,栈2满,但更多的情况是,两个栈见面之时,也就是两个指针之间相差1时,即top + 1 == top2为栈满。

两栈共享空间的结构代码如下:

对于两栈共享空间的push方法,除了要插入元素值外,还需要一个判断是栈1还是栈2的栈号参数stackNumber,代码如下:

 

对于两栈共享空间的pop,也需要判断参数stackNumber,代码如下:

事实上,使用这样的数据结构,通常都是当两个栈的空间需求有相反关系时,也就是一个栈增长时另一个栈在缩短的情况。

4.5 栈的链式存储结构及实现

4.51 栈的链式存储结构(链栈)

栈的链式存储结构,简称链栈由于单链表有头指针,而栈顶指针也是必须的,所以可以结合一下,把栈顶放在单链表的头部,这样单链表的头结点也不需要了。对于链栈来说,基本不存在栈满的情况,除非内存已经没有可以使用的空间,如果真发生,则计算机系统已经面临死机崩溃的情况,而不是链栈是否溢出的情问题。

但对于空栈来说,链表原定义是头指针指向空,那么链栈的空其实是top=NULL的时候。
链栈的结构代码如下:

4.52 栈的链式存储结构——进栈操作

对于链栈的进栈push操作,假设元素值为e的新结点是s,top为栈顶指针,示意图如下:

4.53 栈的链式存储结构——出栈操作

假设变量p用来存储要删除的栈顶结点,将栈顶指针向下移一位,最后释放p即可。如下图所示:

链栈的push和pop没有循环操作,时间复杂度为O ( 1 ) O(1)O(1)。对比顺序栈,两者时间复杂度都为O ( 1 )。二者的区别和线性表一样,如果栈的使用过程中元素变化不可预料,有时很小,有时非常大,那么最好使用链栈,反之如果变化在可控范围内,建议使用顺序栈。

4.6 栈的应用——四则运算表达式求值

4.61 后缀(逆波兰)表示法定义

栈的一个常见的现实应用:数学表达式的求值。“先乘除,后加减,从左算到右,先括号内后括号外”,计算机如何实现?这里的困难在于乘除在加减后,却要先运算,而加入了括号就变得更加复杂。但是括号是成对出现的,所以可以利用栈,碰到左括号时,就将此左括号压入栈,不管表达式有多少重括号,遇到左括号就进栈,而后面出现右括号时就让栈顶的左括号出栈,期间数字运算,栈就由空到有元素,最终再因全部匹配成功成为空栈。
但是对于四则运算,括号只是其中一部分,先乘除后加减仍然复杂。1929年,波兰逻辑学家Jan Lukasiewicz发明了一种不需要括号的后缀表示法,称为逆波兰(Reverse Polish Notation,简称RPN)表示。
先来看对于“9 + ( 3 − 1 ) × 3 + 10 ÷ 2 9+(3-1)×3+10÷29+(3−1)×3+10÷2”,用后缀表示成:
正常的表达式:9+(3-1)×3+10÷2
后缀表达式:9 3 1-3 * + 10 2 / +
“9 3 1-3 * + 10 2 / +”这样的表达式称为后缀表达式因为符号都是在要运算数字的后面出现。

4.62 后缀表达式计算结果

计算机计算后缀表达式:9 3 1-3 * + 10 2 / +

规则:从左到右遍历表达式的每个数字和符号。遇到数字就进栈,遇到符号就将处于栈顶的两个数字出栈进行运算,运算结果进栈,一直到最终获得结果。

4.63 中缀表达式转后缀表达式

接下来推导如何从“9+(3-1)×3+10÷2”变为后缀表达式“9 3 1-3 * + 10 2 / +”。
标准四则运算表达式,即“9+(3-1)×3+10÷2”称为中缀表达式

规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即称为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级不高于栈顶元素(乘除优先于加减)则栈顶元素依次出栈并输出,并将当前符合进栈,一直到最终输出后缀表达式为止。

4.7 队列的定义

队列(queue)是只允许在一端进行插入操作,而在另一端进行删除操作的线性表

队列是一种先进先出(First In First Out)的线性表,简称FIFO。允许插入的一端称为队尾,允许删除的一端称为队头

4.8 队列的抽象数据类型

4.9 循环队列

线性表有顺序储存结构和链式存储结构,队列作为一种特殊的线性表,同样存在这两种存储方式。先来看看顺序存储。

4.91 队列的顺序存储

队列的顺序存储结构实现和线性表完全相同。

假设一个队列有n个元素,则顺序存储需要建立一个大于n的数组,并把队列的所有元素储存在数组的前n个单元,数组下标为0的一端是队头。所谓的入队列操作就是在队尾追加一个元素,不需要移动任何元素,因此时间复杂度是O ( 1 ) 。

而出列在队头,意味着队列的所有元素都得往前移动,时间复杂度为O ( n ) O(n)O(n)。

然后出队列时也可以不全部移动元素,如果不限制队列的元素存储在数组的前n个单元这一条件,出队的性能大大增加。如下图所示。


为了避免只有一个元素时,队头和队尾重合,引入两个指针,front指针指向队头元素,rear指针指向队尾的下一个位置。当front等于rear时,表示空队列

 4.92 循环队列的定义

解决假溢出的办法就是后面满了,就再从头开始,也就是头尾相接的循环。我们把队列头尾相接的顺序存储结构称为循环队列
继续刚才的例图,将rear指针改为指向下标为0的位置,这样就不会造成指针指向不明的情况了,如下图所示。

接着入队a 6 将它置于下标为0处,rear指针指向下标为1处,如左下图所示。若再入队a 7 ,则rear和front指针重合,如右下图所示。

由于rear可能比front大,也可能比front小,所以尽管只相差一个位置时也可能相差整整一圈。若队列的最大尺寸为QueueSize,那么队列满的条件是(rear+1)%QueueSize == front(取模的目的在于整合rear和front大小为一个问题)。比如QueueSize = 5,左上图中front = 0,rear = 4,(4+1)%5=0,所以此时的队列满。再比如下图,front = 2,rear = 1,(1+1)%5=1≠2,所以队列没有满。

当rear > front时,此时的队列长度为rear - front;当rear < front时,队列长度分为两个部分,QueueSize - front和0 + rear,加在一起得rear - front + QueueSize。
因此通用的计算队列长度得公式为:
(rear - front + QueueSize)%QueueSize

4.10 队列的链式存储结构及实现

队列的链式存储结构其实就是线性表的单链表,只不过它只能尾进头出,简称为链队列。为了操作方便,将队头指针指向链队列的头结点,而队尾指针指向终端结点。

4.10.1 队列的链式存储结构——入队操作

4.10.2 队列的链式存储结构——出队操作

代码如下:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值