1. 表达式的种类

如何将表达式翻译成能够正确求值的指令序列,是语言处理程序要解决的基本问题,作为栈的应用事例,下面介绍表达式的求值过程。 任何一个表达式都是由操作数(亦称运算对象)、操作符(亦称运算符)和分界符组成的。通常,算术表达式有3种表示: ①中缀(infix)表示:<操作数><操作符><操作数>,如A+B。 ②前缀(prefix)表示: <操作符><操作数><操作数>,如+AB。 ③后缀(postfix)表示: <操作数><操作数><操作符>,如AB+。

2. 求解算法

在中缀表达式中操作符的优先级和括号使得求值过程复杂化,把它转换成后缀表达式,可以简化求值过程。但是,老师使用的PPT上的示例显然是直接对中缀表达式进行求值,并未进行转换。因此我也采用“直接求值法”。为了实现对中缀表达式的求值,需要考虑各操作符的优先级,参见表1。

操作符ch # ( *、/、% +、- )
isp 0 1 5 3 6
icp 0 6 4 2 1

表1 各个运算符的优先级

表中的isp叫做栈内优先级(in stack priority),icp叫做栈外优先级(in coming priority)。为什么要如此设置呢?这是因为某一操作符按照算术四则运算有一个优先级,这是icp。一旦它进入操作符栈,它的优先级要提高,以体现优先级相同的操作符先来的先做,就是说,在表达式中运算优先级相同的必须自左向右运算,这就是栈内优先级isp。
  从上表可以看到,左括号“(”的栈外优先数最高,它一来到立即进栈,但当它进入栈中后,其栈内优先数变得极低,以便括号内的其他操作符进栈。除它之外,其他操作符进入栈中后优先数都升1,这样可以体现中缀表示中相同优先级的操作符自左向右计算的要求,让位于栈顶的操作符先退栈输出。操作符优先数相等的情况只出现在“(”与栈内“)”括号配对或栈底的“#”号与表达式输入最后的“#”号配对时。前者将连续推出位于栈顶的操作符,直到遇到“)”为止。然后将“(”退栈以对消括号,后者将结束算法。
  扫描中缀表达式,并求值的算法描述如下:
  1)操作符栈初始化,将结束符“#”进栈;操作数栈初始化。然后读入中缀表达式字符流中的首字符 ch。
  2)重复执行以下步骤,直到 ch=“#”,同时栈顶的操作符也是“#”,停止循环。
  ①若 ch 是操作数,将压入操作数栈,读入下一个字符 ch。
  ②若 ch 是操作符,比较ch的优先级 icp 和操作符栈当前栈顶的操作符 op 的优先级 isp:
  • 若 icp(ch)> isp(op),令ch进栈,读入下一个字符ch。
  • 若 icp(ch)≤ isp(op),退出运算数栈的两个元素a和b计算:a op b,将计算结果压入运算数栈中。
  • 若 ch = “)”或 op = “(”,退出运算符栈的栈顶元素,读入下一字符ch。
  3)算法结束,运算数栈的栈顶元素即为所求中缀表达式的结果。
  以上算法是我结合在资料上看到的关于中缀表达式转换为后缀表达式的算法,自己琢磨出来的。算法的正确性有待证明,但实现在计算几个样例时,结果正误均有。具体结果将在第四部分展示。
  另外,我在博客和《算法导论》上都看到所谓“双栈算术表达式求值算法”的介绍与大概思路:
  双栈算术表达式求值算法是由E.W.Dijkstra在上个世纪60年代发明的一个很简单的算法,用两个栈【一个用来保存运算符、一个用来保存操作数】来完成对一个表达式的运算。其实整个算法思路很简单:
  • 无视左括号
  • 将操作数压入操作数栈
  • 将运算符压入运算符栈
  • 在遇到右括号的时候,从运算符栈中弹出一个运算符,再从操作数栈中弹出所需的操作数,并且将运算结果压入操作数栈中
---------------------
作者:erzhanchen
来源:CSDN
原文:https://blog.csdn.net/erzhanchen/article/details/57421267
版权声明:本文为博主原创文章,转载请附上博文链接!
为了表示对原作者的尊重,我保留了“犯罪痕迹”。

3. 核心代码

在正式贴出核心代码(完整代码将写在下一篇博客)前先进行一些必要说明: ①从右向左扫描表达式字符串; ②C类型的字符串开始下标是0,字符串的最后一个元素是“\0”;

D a t a ' ' S t r u c t u r e '\0'
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14

③假设“1”、“2”、“3”、“4”、“5”这五个数字组成一个十进制的整数,从左到右依次是万位数、千位数、百位数、十位数、个位数,那么计算这个整数的表达式就是下面这样的: 1×104 +2×103+3×102+4×101+5×100=12345

 void SeqStack::calculate( char* Str )///C类型字符串表达式为参数传入方法中
{
charSeqStack css1;///css1 用作操作符栈
char ch1;///访问操作符栈栈顶元素时,保存栈顶元素
int i = ;///用来保存当前扫描的表达式位置
double a, b;///用来保存临时运算时调用对象栈栈顶的前两个元素 int cnt = ;///记录字符串中数字连续时的位数,即计算 10 的 n 次方时幂的指数
///比方说:在读入整数156时,读“1”这个字符之前读了两个数字“5”、“6”,那么读1的时候,这个1就代表100的意思。
int temp = ;///保存扫描到的数值 while ( Str[i] != '\0' )///确定表达式的终止下标
{
i ++;
}///比方说:在扫描表达式“#1+1#”时,表达式字符串字符最大下标是5,但是表达式终止下标是3
i = i - ;///C类型字符串下标从0开始,字符串的最后一位是'\0' ///从右向左扫描表达式,当 操作符栈不为空 或 未扫描到表达式的最左边 时 循环
while( css1.topValue() != - || Str[i] != '#' )
{
char ch = Str[i];///读取扫描到的当前字符
if ( isdigit(ch) )///利用C语言的库函数 isdigit() 判断字符 ch 是否为数字。
///库函数 isdigit() 声明在头文件 ctype.h 中
///算法步骤 2)的情况 ①,如果 ch 是数字就将其转换为对应的十进制数
{
temp = temp + pow( , cnt ) * int( ch - '' );///类似于 m 进制 转换为 10 进制数时的操作
/// C语言的 pow() 函数定义在 math.h 中,pow( 10, cnt ) 计算的是 10 的 cnt 次方
cnt ++;///位数增一
i --;///继续向左扫描表达式
}
else///如果 ch 不是数字,是运算符
{
if (cnt)///C语言中非零值的表达式布尔值都为真,如果这个表达式为真说明原来扫描到了表达式中的数值
{
push(temp);///将数值压入操作数栈中,操作数栈是调用对象中的栈
temp = ;///在未扫描到新的数字前置零
cnt = ;///在未扫描到新的数字前位数置零
}
css1.getTop(ch1);///读取操作符栈的栈顶
if ( ch1 == ')' && ch == '(' )///算法步骤 2)的情况 ② 的第三种
{
css1.pop();///将运算符栈栈顶元素退出
i --;///继续向左扫描表达式
continue;
}
if ( isp(ch1) < icp(ch) )///算法步骤 2)的情况 ② 的第一种
{
css1.push(ch);///将当前扫描到的操作符 ch 压入操作符栈
i --;
}
else if (isp(ch1) >= icp(ch))///算法步骤 2)的情况 ② 的第三种
{
getTop(a);///获取操作数栈栈顶元素
pop();///弹栈,以便获取第二个元素
getTop(b);///获取操作数栈栈顶元素
pop();///弹栈,已计算过不用再保留
push( doOperator( a, b, ch1 ) );///将计算结果压入运算数栈中
css1.pop();///退栈,已使用过不再保留
}
}
} if (cnt)///细节处理:防止以数字为结尾的字符串,比方说 #1#,没有这个判断的话,输入 #1# 时就会出错:因为1没有被压入操作数栈中
{
push(temp);
}
/*
#1#
#1+1#
#2*2+3#
#(1)#
写代码的时候就是按照让这4个表达式都能正常运行的思路来写的
*/
}

4. 说明及其他

void SeqStack::calculate( char* Str ) 这个方法我原来放置的参数是两个:charSeqStack& css1, charSeqStack& css2 , css1 是运算符栈,css2 是表达式栈。后面发现这么实现的话只能处理一位数的四则运算。经过考量,决定用C语言型字符串代替作为参数。

被调用函数在获取C语言型数组与字符串时无法得知其长度,因此第一个while循环作用是确定表达式字符串的长度。第二个while循环从右向左扫描字符串,对每个ch判断后按算法进行相应的操作。

值得一提是,每扫描到一个数字,就通过表达式 temp = temp + pow( 10, cnt ) * int( ch - ‘0”); 将当前连续数字所表达的确切十进制值计算出来,并保存在临时变量temp中。另外,变量cnt的作用是表示当前有几个连续的数字。当数字连续终止时(如图1所示),将计算结果temp压入调用对象ss1的栈(即操作数栈)中。

最后展示一下程序的运行截图并简要说明:

控制台第一行打印的数值为使用形如以下方式得到的结果:

cout << 200+500*(200+300)*600/709-400 << endl;

即第一个待求解表达式由C++表达式计算所得结果,以用于与实现得出的结果作比较。

第1次测试:

第一个待求解表达式实现得出的结果比由C++表达式计算的结果大1,错误。

第2次测试:

第一个待求解表达式实现得出的结果与由C++表达式计算的结果完全一致;

第4次测试: 第一个待求解表达式实现得出的结果比由C++表达式计算的结果大1582,错误。

综上所述,实现用于计算一些表达式是正确可行的,而对于另外一些表达式则正确得出结果。另,由实现计算5/3*9与(5/3)*9的结果知:是否添加括号对实现能否正确计算表达式有直接关系;对于不能正确计算的表达式,不同编译器生成的可执行文件得到的结果也不同(如图7与图8所示)。 “栈的应用:后缀表达式求值”算法的实现还是有bug。

图1 表达式字符串中连续数字示意

图3 求解111+56*(789+29)*5/80-400与12+5*(2+3)*6/2-4程序运行截图

图4 求解222+555*(777+111)*666/888-999/333+444+(34%7)与(5/3)*9程序运行截图

图5 求解222+555*(777+111)*666/888-999/333+444+(347)与(5/3)*9程序运行截图

图6 计算222+555*(777+111)*666/888-999/333+444+(347)与9%3程序运行截图

图7 Code::Blocks 17.12 编译的可执行文件计算200+500*(200+300)*600/709-400程序运行截图

图8 VC6.0 编译的可执行文件计算200+500*(200+300)*600/709-400程序运行截图

图9 计算12+5*(2+3)*6/2-4时操作数栈与操作符栈的变换情况

刁肥宅详解中缀表达式求值问题:C++实现顺序/链栈解决的更多相关文章

  1. C语言中缀表达式求值(综合)

    题前需要了解的:中缀.后缀表达式是什么?(不知道你们知不知道,反正我当时不知道,搜的百度) 基本思路:先把输入的中缀表达式→后缀表达式→进行计算得出结果 栈:"先进先出,先进后出" ...

  2. 第四章 栈与队列(c4)栈应用:中缀表达式求值

  3. FZU2215 Simple Polynomial Problem(中缀表达求值)

    比赛时没做出这题太可惜了. 赛后才反应过来这就是个中缀表达式求值,数字栈存的不是数字而是多项式. 而且,中缀表达式求值很水的,几行就可以搞定. #include<cstdio> #incl ...

  4. C++之字符串表达式求值

    关于字符串表达式求值,应该是程序猿们机试或者面试时候常见问题之一,昨天参加国内某IT的机试,压轴便为此题,今天抽空对其进行了研究. 算术表达式中最常见的表示法形式有 中缀.前缀和 后缀表示法.中缀表示 ...

  5. HNU 12817 Shipura(表达式求值)

    题目链接:http://acm.hnu.cn/online/?action=problem&type=show&id=12817 解题报告:定义两种运算符号,一种是>>,就 ...

  6. 信息竞赛进阶指南--递归法求中缀表达式的值,O(n^2)(模板)

    // 递归法求中缀表达式的值,O(n^2) int calc(int l, int r) { // 寻找未被任何括号包含的最后一个加减号 for (int i = r, j = 0; i >= ...

  7. NYOJ 35 表达式求值(逆波兰式求值)

    http://acm.nyist.net/JudgeOnline/problemset.php?typeid=4 NYOJ 35 表达式求值(逆波兰式求值) 逆波兰式式也称后缀表达式. 一般的表达式求 ...

  8. SDIBT2666——逆波兰表达式求值

    逆波兰表达式求值(栈和队列) Description 从键盘上输入一个逆波兰表达式,用伪码写出其求值程序.规定:逆波兰表达式的长度不超过一行,以@符作为输入结束,操作数之间用空格分隔,操作符只可能有+ ...

  9. CH BR4思考熊(恒等有理式-逆波兰表达式求值)

    恒等有理式 总时限 10s 内存限制 256MB 出题人 fotile96 提交情况 4/43 描述 给定两个有理式f(X)与g(X),判断他们是否恒等(任意A,如果f(A)与g(A)均有定义,那么f ...

随机推荐

  1. iOS 监测电话呼入

    1.首先引入CoreTelephony框架,代码里: @import CoreTelephony; 项目设置里: 2.定义属性,建立强引用: @property (nonatomic, strong) ...

  2. Python: How to iterate list in reverse order

    #1 for index, val in enumerate(reversed(list)): print len(list) - index - 1, val #2 def reverse_enum ...

  3. c#内存管理,垃圾回收和资源释放

    <1>关于虚拟内存的概念 Windows使用一个虚拟寻址系统,该系统把程序可用的内存地址映射到硬件内存中的实际地址上去,这些任务完全由windows后台管理,其实际结果是32位处理机上的每 ...

  4. 各 Android 平台版本支持的 API 级别

    平台版本 API 级别 VERSION_CODE 备注 Android 7.0 24 N 平台亮点 Android 6.0 23 M 平台亮点 Android 5.1 22 LOLLIPOP_MR1 ...

  5. CF749C Voting

    题目链接: http://codeforces.com/problemset/problem/749/C 题目大意: 共有n个人,编号为1~n.他们每个人属于且仅属于R阵营或N阵营中的一个.现在他们要 ...

  6. Xilinx HLS

    Xilinx 的高层次综合(High Level Synthesis, HLS)技术是将C/C++/SystemC软件语言转换成Verilog或VHDL硬件描述语言的技术.现已应用在SDAccel,S ...

  7. Centos5安装***

    最近shadowsocks挺火,看了几张帖子,感觉在手机上应该挺好用,电脑都是挂着ssh,用不到***了,下面贴出服务器安装过程: yum install build-essential autoco ...

  8. Docker - Image创建

    自己创建Image会有一些好处,可以选择最新的版本,而且从国内的镜像创建时更新软件也会从该镜像获取,速度更快. (1)安装debootstrap zhouh1@uhome:/media/zhouh1/ ...

  9. 正确使用MySQL JDBC setFetchSize()方法解决JDBC处理大结果集 java.lang.OutOfMemoryError: Java heap space

    昨天在项目中需要对日志的查询结果进行导出功能. 日志导出功能的实现是这样的,输入查询条件,然后对查询结果进行导出.由于日志数据量比较大.多的时候,有上亿条记录. 之前的解决方案都是多次查询,然后使用l ...

  10. 十分钟搭建App主流框架

    搭建主流框架界面 0.达成效果 Snip20150904_5.png 我们玩iPhone应用的时候,有没发现大部分的应用都是上图差不多的结构,下面的TabBar控制器可以切换子控制器,上面又有Navi ...