数据结构-栈--表达式

数据结构之栈在表达式求值及转换中的应用

1 栈及表达式的基本定义

1.1栈的定义

栈(stack)是限定仅在表尾进行插入或删除操作的线性表。因此,对栈来说,
表尾端有其特殊含义,称为栈顶(top),相应地,表头端称为栈底(bottom)。不含元素的空表称为空栈。栈也称为后进先出表。下面将给出关于栈基本操作的相关函数:
InitStack(&S):构造一个空栈S
GetTop(S,&e):用e返回S的栈顶元素。
Push(&S,e):插人元素e为新的栈顶元素。
Pop(&s,&e):删除S的栈顶元素,并用e返回其值。
Precede(m,n):运算符的优先级比较函数。

1.2表达式的定义

任何一个表达式都是由操作数(operand)、运算符(operator)和界限符(elimite)组成的,我们称它们为单词。一般地,操作数既可以是常数也可以是被说明为变量或常量的标识符;运算符可以分为算术运算符、关系运算符和逻辑运算符3类;基本界限符有左右括号和表达式结束符等。

1.3表达式的分类

表达式一般分为前缀表达式,中缀表达式和后缀表达式,在计算机中,计算后缀表达式值的时间空间消耗往往比计算中缀表达式的消耗少,因此我们通常将普通表达式转化为某种类型表达式来达到减少复杂度的目的。
1.3.1 前缀表达式 前缀表达式是将运算符写在前面,操作数写在后面。为纪念其发明者波兰数学家Jan Lukasiewicz,前缀表达式也称为“波兰式”。
1.3.2 中缀表达式 中缀表达式是指运算符在两个操作数的中间。即平时常用的表达式脱括号后的形式。
1.3.3.后缀表达式 后缀表达式是将运算符放在两个运算对象的后面,计算时按运算符出现的顺序,严格从左向右进行(不再考虑运算符的优先规则)。运用次数最多,又称“逆波兰式”。
对于算术表达式Exp=ab+(c-d/e)f,它的三种表达形式为:前缀式:+ab-c/def,中缀式:ab+c-d/ef,后缀式:abcde/-f+。

1.4关于上述表达式的相关结论

1.操作数之间的相对次序不变;
2.运算符的相对次序不同;
3.中缀式丢失了括弧信息,致使运算的次序不确定;
4.前缀式的运算规则为:连续出现的两个操作数和在它们之前且紧靠它们的运算符构成一个最小表达式;
5.后缀式的运算规则为:运算符在式中出现的顺序恰为表达式的运算顺序;每个运算符和在它之前出现且紧靠它的两个操作数构成一个最小表达式。

2 栈在表达式求值过程以及转换中的应用

2.1 表达式求值算法的基本思路

为了叙述的简洁,我们仅讨论简单算术表达式的求值问题。这种表达式只含加、减、乘、除4种运算符。读者不难将它推广到更一般的表达式上。
2.1.1 求值算法中的主要数据结构 我们分别用顺序栈来寄存表达式的操作数和运算符。为了实现算符优先算法,可以使用两个工作栈。一个称做OPTR,用以寄存运算符;另一个称做OPND,用以寄存操作数或运算结果。
2.1.2 关于表达式的分析与判断 表达式求值程序的关键是对数字与运算符的判断和运算符优先级的判断,以及出栈的运算。可以建立两个栈,分别存储数字与运算符,栈OPTR存运算符,栈OPND存数字。依次读取表达式的字符串。读取字符的时候需要判断字符的类型,先判断是数字还是运算符:
第一步,如果是数字,把数字压入栈OPND。
第二步,如果是运算符,那么在压入运算符之前,先将要压入的与栈顶的运算符优先级相比较,此时有三种情况:
1.如果当前的运算符优先级等于栈顶,则脱括号并接受下一字符;
2.若当前的运算符优先级大于栈顶的,则压入;
3.若当前的运算符优先级小于栈内时,弹出栈顶的运算符,同时弹出两组数字,经过运算符的运算后再重新压到栈内。
为了方便判断运算结束,在存储运算符之前先将“#”压入栈OPTR中,在输入表达式时以“#”结束,所以当接受的运算符为‘#’并且OPTR栈顶的元素也为‘#’来作为运算结束的条件,经过上述的讨论,最后栈OPND的数值,即为表达式求值的最终结果。
2.1.3 表达式求值例题分析
例1:利用上述算法对算数表达式3*(7-2)求值
下图中,左侧为OPTR栈,右侧为OPND栈。
步骤1:“#”入OPTR栈
在这里插入图片描述

                                        图1

步骤2:“3”入OPND栈
在这里插入图片描述

                               图2

步骤3:“”比此时的OPTR栈的顶元素“#”的优先权高,“”入OPTR栈

步骤4.:“(”比此时的OPTR栈顶元素“*”的优先权高,“(”入OPTR栈

步骤5:“7”是数字,进OPND栈

步骤6.:“-”比此时的OPTR栈顶元素“(”的优先权高,“-”入OPTR栈

步骤7.:“2”入OPND栈
请添加图片描述

            图7

步骤8.:“)”比此时的OPTR栈顶元素“-”的优先权低,执行Pop(OPTR,theta);操作,“-”退OPTR栈,OPND栈中退出两个数,在该步操作中执行“7-2”,把得到的结果5压入OPND栈中,此时两个栈的元素为下图:
请添加图片描述

                               图8

步骤9:“)”继续和OPTR栈此时的栈顶元素“(”相比,发现相等,则脱括号,并接受下一字符,此时的OPND栈为:
在这里插入图片描述

                图9

步骤10:我们通常以“#”来作为表达式的末尾,所以最后需要判断优先级大小的是“#”和OPTR的栈顶元素“”,此时发现栈顶元素“”的优先级高,则栈顶元素“”出栈,OPND栈中出两个元素,执行“35”操作,得到15,压入OPND栈内,最后两个“#”相等,while循环结束。则OPND栈中的元素就是该表达式的最终求值。此时,两个栈的元素为:
在这里插入图片描述

                 图10

最后结果为3*(7-2)=15。
2.1.4 算法时间和空间性能分析
1.时间复杂度:对于含n个字符的表达式,无论是对其进行合法性检测还是对其进行入栈出栈操作n次,因此其时间复杂度为0(n)。
2.空间复杂度:由于在本程序中,在为算符栈(OPTR)和操作数栈(0PND)涉及到两种情况时申请空间,一方面分别为OPTR栈和0PND栈申请了初始的存储单元,均为STACKINITSIZE=100个;另一方面,考虑到两个栈在处理具体的算术表达式时,有可能会出现溢出的情况,即栈的初始的存储空间不够用,这时需要为其申请额外的存储空间,每溢出一次,就为其申请存储单元STACKINCREMENT=10个,所以,本程序的算法的空间复杂度一方面取决于算术表达式的长度,另一方面取决于本程序的所有代码所占用的存储空间大小。

2.2 后缀表达式求值算法
2.2.1求值算法中的主要数据结构 根本思想就是先找运算符,再找操作数,因为操作数来的早晚与运算顺序无关,所以,我们可以设置一个栈来存储操作数,只要是操作数据进栈,只要是运算符就从栈中取出两个操作数,经过运算符的运算后再重新压到栈内,最后栈顶的值就是我们要求的最后结果。

2.2.2基于C语言的算法
下面给出其基本算法:
int GetValue NiBoLan(char *str)/对逆波兰式求值
{ p=str;InitStack(s); //s为操作数栈
while(*p!="#’)
{ if(Isdigit(*p)) push(s,*p);
else {
pop(s,a);pop(s,b);
r=compute(b,*p,a); //假设compute为执行双目运算的过程
push(s,r);}//else
}//while
}//GetValue_ NiBoLan

2.3 原表达式求后缀表达式算法

2.3.1 原表达式求后缀式算法思想 因为运算符来的早晚与运算顺序无关,首先设立暂存运算符的栈,设表达式的结束符号为“#”,预设运算符栈的栈底为“#”,若当前字符是操作数,则直接发送给后缀式;若当前运算符的优先数高于栈顶运算符,则进栈;否则,退出栈顶运算行发送给后缀式;“(”对它之前后的运算符起隔离作用,“)”可视为自相应左括弧开始的表达式的结束符。
2.3.2 原表达式求后缀式例题分析
对原表达式2*(9+6/3-5)+4求其后缀式:
在这里插入图片描述

最后求得后缀表达式为:2963/+5-*4+。

参考文献:
[1] 严蔚敏,吴伟民.数据结构C语言版[M].北京:清华大学出版社。

这是自己总结的表达式求值的基本算法,也是自己的写的一篇很简单的课程论文,主要是给出了相应的例题分析,图片是自己做的,不是很美观,有错误欢迎大家评论区指出讨论,大家一起进步!

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值