数据结构与算法分析笔记(c++)_栈ADT

3.6 栈ADT
栈(stack)是限制插入和删除操作只能在一个位置上进行的表,该位置是表的末端,称为栈的顶(top)。对栈的基本操作是push(进栈)和pop(出栈),前者相当于插入,后者则是删除最后插入的元素。最后插入的元素可以通过使用top例程在执行pop之前进行访问。栈有时又称为LIFO(后进先出)表。普通的清空栈的操作和判断是否空栈的测试都是栈的操作指令系统的一部分,但是,对栈所能够做的所有操作基本上就是push和pop操作
在这里插入图片描述

一般的模型是,存在某个元素位于栈顶,而该元素是唯一的可见元素。
在这里插入图片描述

3.6.2栈的实现
由于栈是一个表,因此任何实现表的方法都能实现栈。很明显list和 vector支持栈操作;
1.栈的链表实现
栈的第一种实现方法是使用单向链表。我们通过在表顶端插入元素来实现push,通过删除表顶端元素实现pop。top操作只是访问表顶端元素并返回它的值。有时pop操作和top操作合二为一
2.栈的数组实现
另一种可选的实现避免了使用链并且可能是更流行的解决方案。由于使用 vector中的back push_back和 pop back实现,因此这个实现很简单。每个栈有一个 thearray和一个 topofStack,对于空栈其值为-1(这也就是空栈的初始化)。为了将某个元素x压入到栈中,将 topofstack加1,然后置 thearray[topofstack]=x。为了弹出栈元素,置pop函数的返回值为thearray[topofstack],然后将 topofstack减1。
注意,这些操作不仅以常数时间运行,而且是以非常快的常数时间运行。
最现代化的计算机将栈操作作为其指令系统的一部分,这个事实强化了这样一种思想,即在计算机科学中,栈很可能是继数组之后的最基本的数据结构。
3.6.3应用
1.平衡符号
编译器检查程序的语法错误,但是常常缺少一个符号。一个有用的工具就是一个检验是否所有的东西都成对出现的程序
这个简单的算法用到一个栈,叙述如下:做一个空栈。读入字符直至文件尾。如果字符是一个开放符号,则将其压入栈中如果字符是一个封闭符号,那么若栈为空,则报错;若栈不为空,则将栈元素弹出。如果弹岀的符号不是对应的开放符号,则报错。在文件尾,如果栈非空则报错
2.后缀表达式
在这里插入图片描述
该例子的典型计算顺序是将499和1.06相乘并存为A1,然后将599和A1相加,再将结果存入A1;再将699和106相乘并将答案存为A2,最后将A1和A2相加并将最后结果放入A1。可以将这种操作顺序书写如下
在这里插入图片描述
这种记法叫作后缀(postfix)或逆波兰记法(reverse polish notation),其求值过程恰好就是上面所描述的过程。计算这个问题最容易的方法是使用栈。当遇到一个数时就把它压入栈中;在遇到一个操作符时该操作符就作用于从该栈弹出的两个数(符号)上,再将所得结果压入栈中。
计算一个后缀表达式花费的时间是O(M),因为对输入中的每个元素的处理都是出一些栈操作组成从而花费常数时间。该算法的计算是非常简单的。注意,当一个表达式以后缀记法给出时,没有必要知道任何优先规则:这是一个明显的优点。
3.中缀到后缀的转换
栈不仅可以用来计算后缀表达式的值,而且还可以用来将一个标准形式的表达式(或叫作中缀式(infix))转换成后缀式。通过只允许操作符“+”“”、“(”、“)”,并坚持普通的优先规则而将一般的问题浓缩成小规模的问题。还要进一步假设表达式是合法的。
规则:当读到一个操作数的时候,立即把它放到输出中。操作符不立即输出,从而必须先存在栈。当遇到左圆括号时我们也要将其推入栈中。如果见到一个右括号,那么就将栈元素弹出,将符号写出直到遇到一个(对应的)左括号,但是这个左括号只被弹出并不输出。
如果见到任何其他的符号(“+”“
”、“(”),那么从栈中弹出栈元素直到发现优先级更低的元素为止。当从栈中弹出元素的工作完成后,我们冉将操作符压入栈中。最后,如果读到输入的末尾,将栈元素弹出直到该栈变成空栈,将符号写到输出中
在这里插入图片描述
首先,a被读入,并送到输出。然后,“+”被读入并压入栈中。接下来b读入并送到输出
在这里插入图片描述
接着“κ”号读入。操作符栈的栈顶元素比“”的优先级低,故没有输出且“”进栈。接着,c被读入并输出
在这里插入图片描述
后面的符号是一个“+”号。检查一下栈可以发现,需要将“
”从栈中弹出并放到输出中;弹出栈中剩下的“+”号,该操作符不比刚刚遇到的“+”号优先级低而是有相同的优先级;然后,将刚刚遇到的“+”号压入栈中。
在这里插入图片描述
下一个读到的符号是一个“(”,由于有最高的优先级,因此它被放进栈中。然后,d读入并输出。
在这里插入图片描述
继续进行,又读到一个“*”。由于除非正在处理闭括号,否则开括号不会从栈中弹出,因此没有输出。下一个是e,它被读入并输出。

在这里插入图片描述
再往后读到的符号是“+”。将“*”弹出并输出,然后将“+”压入栈中。这以后,读到f并输出。在这里插入图片描述
在这里插入图片描述
与前面相同,这种转换只需要O(N)时间并经过一次输入后完成工作。
4.函数调用
当存在函数调用的时候,需要存储的所有重要信息,诸如寄存器的值(对应变量的名字)和返回地址(它可从程序计数器得到,一般情况下是在寄存器中)等,都要以抽象的方式存在“一张纸上”并被置于一个堆 的顶部。然后控制转移到新函数,该函数自由地用它的值代替这些寄存器。如果它又进行其他的函数调用,那么也遵循相同的过程。当该函数要返回时,它查看堆顶部的那张“纸”并复原所有的寄存器。然后它进行返回转移
该问题类似于平衡符号问题的原因在于,函数调用和函数返回基本上类似于开括号和闭括号,者想法是一样的
显然,所有工作均可由一个栈来完成。所存储的信息或称为活动记录(activation record),或称为栈帧(stack frame)。
在实际计算机中,栈常常是从内存分区的高端向下增长,而在许多系统中是不检测溢出的。由于同时有太多的正在运行着的函数,因此用尽栈空间的情况总是可能发生的。勿庸置疑,用尽栈空间总是致命的错误。
在不进行栈溢出检测的语言和系统中,程序将会没有明确说明地崩溃。在正常情况下不应该用尽栈空间;发生这种情况通常意味着有失控递归(忘记基准情形)的存在。另一方面,某些完全合法并且表面上无害的程序也可能用尽栈空间(元素太多)。
尾递归(tail recursion),是极差的使用递归的例子。尾递归指的是在最后一行的递归调用。
虽然非递归程序般说来确实比等价的递归程序要快,但是速度优势的代价却是由于去除递归而使得程序的清晰性受到了影响

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值