刁肥宅详解中缀表达式求值问题:C++实现顺序/链栈解决

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

 1 void SeqStack::calculate( char* Str )///C类型字符串表达式为参数传入方法中
 2 {
 3     charSeqStack css1;///css1 用作操作符栈
 4     char ch1;///访问操作符栈栈顶元素时,保存栈顶元素
 5     int i = 0;///用来保存当前扫描的表达式位置
 6     double a, b;///用来保存临时运算时调用对象栈栈顶的前两个元素
 7
 8     int cnt = 0;///记录字符串中数字连续时的位数,即计算 10 的 n 次方时幂的指数
 9     ///比方说:在读入整数156时,读“1”这个字符之前读了两个数字“5”、“6”,那么读1的时候,这个1就代表100的意思。
10     int temp = 0;///保存扫描到的数值
11
12     while ( Str[i] != ‘\0‘ )///确定表达式的终止下标
13     {
14         i ++;
15     }///比方说:在扫描表达式“#1+1#”时,表达式字符串字符最大下标是5,但是表达式终止下标是3
16     i = i - 2;///C类型字符串下标从0开始,字符串的最后一位是‘\0‘
17
18     ///从右向左扫描表达式,当 操作符栈不为空 或 未扫描到表达式的最左边 时 循环
19     while( css1.topValue() != -1 || Str[i] != ‘#‘ )
20     {
21         char ch = Str[i];///读取扫描到的当前字符
22         if ( isdigit(ch) )///利用C语言的库函数 isdigit() 判断字符 ch 是否为数字。
23         ///库函数 isdigit() 声明在头文件 ctype.h 中
24         ///算法步骤 2)的情况 ①,如果 ch 是数字就将其转换为对应的十进制数
25         {
26             temp = temp + pow( 10, cnt ) * int( ch - ‘0‘ );///类似于 m 进制 转换为 10 进制数时的操作
27             /// C语言的 pow() 函数定义在 math.h 中,pow( 10, cnt ) 计算的是 10 的 cnt 次方
28             cnt ++;///位数增一
29             i --;///继续向左扫描表达式
30         }
31         else///如果 ch 不是数字,是运算符
32         {
33             if (cnt)///C语言中非零值的表达式布尔值都为真,如果这个表达式为真说明原来扫描到了表达式中的数值
34             {
35                 push(temp);///将数值压入操作数栈中,操作数栈是调用对象中的栈
36                 temp = 0;///在未扫描到新的数字前置零
37                 cnt = 0;///在未扫描到新的数字前位数置零
38             }
39             css1.getTop(ch1);///读取操作符栈的栈顶
40             if ( ch1 == ‘)‘ && ch == ‘(‘ )///算法步骤 2)的情况 ② 的第三种
41             {
42                 css1.pop();///将运算符栈栈顶元素退出
43                 i --;///继续向左扫描表达式
44                 continue;
45             }
46             if ( isp(ch1) < icp(ch) )///算法步骤 2)的情况 ② 的第一种
47             {
48                 css1.push(ch);///将当前扫描到的操作符 ch 压入操作符栈
49                 i --;
50             }
51             else if (isp(ch1) >= icp(ch))///算法步骤 2)的情况 ② 的第三种
52             {
53                 getTop(a);///获取操作数栈栈顶元素
54                 pop();///弹栈,以便获取第二个元素
55                 getTop(b);///获取操作数栈栈顶元素
56                 pop();///弹栈,已计算过不用再保留
57                 push( doOperator( a, b, ch1 ) );///将计算结果压入运算数栈中
58                 css1.pop();///退栈,已使用过不再保留
59             }
60         }
61     }
62
63     if (cnt)///细节处理:防止以数字为结尾的字符串,比方说 #1#,没有这个判断的话,输入 #1# 时就会出错:因为1没有被压入操作数栈中
64     {
65         push(temp);
66     }
67      /*
68     #1#
69     #1+1#
70     #2*2+3#
71     #(1)#
72     写代码的时候就是按照让这4个表达式都能正常运行的思路来写的
73     */
74 }

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时操作数栈与操作符栈的变换情况

原文地址:https://www.cnblogs.com/25th-engineer/p/9902776.html

时间: 2024-10-21 20:01:26

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

中缀表达式求值问题

           中缀表达式求值问题 中缀表达式的求值问题是一个比较常见的问题之一,我们通常在编写程序时,直接写出表达式让编译器去处理,很少去关心编译器是怎么对表达式进行求值的,今天我们来一起了解一下其中具体的原理和过程. 表达式一般来说有三种:前缀表达式.中缀表达式.后缀表达式,其中后缀表达式又叫做逆波兰表达式.中缀表达式是最符合人们思维方式的一种表达式,顾名思义,就是操作符在操作数的中间.而前缀表达式和后缀表达式中操作符分别在操作数的前面和操作数的后面.举个例子: 3+2 这个是最简单的

中缀表达式求值 C++ Stack

给一个包含小数的中缀表达式 求出它的值 首先转换为后缀表达式然后利用stack求出值 转换规则: 如果字符为'('  push else if 字符为 ')' 出栈运算符直到遇到'(' else if 字符为'+','-','*','/' { if 栈为空或者上一个运算符的优先级小于当前运算符 push else { 运算符优先级小于等于栈顶运算符的优先级,出栈 然后!将当前运算符入栈! } } 代码 #include<iostream> #include<cstdio> #inc

中缀表达式求值总结

中缀表达式的题目困扰了我两三年,都没去写过..这两天看到2005年提高组的T3要用到所以心血来潮写了一下. 表达式求值借助基本结构应该不用说了是栈,不管手写还是STL都没有太大关系.而中缀表达式最难控制的地方是优先级,算上+-*/^()一共有四个优先级[+-,*/,, ^()](后面会提到一个三级的字符“负号”,这是预留空位). 下面对一个例子进行分析:2*3+4*6/3+17-4^2 那么处理的时候怎么控制优先级呢? 搜到‘+’之前,栈的情况是这样的: 操作数栈:2  3 操作符栈:* 然后搜

中缀表达式求值

中缀表达式用于计算一个表达式,比如计算器 就是这样实现的 这儿是用栈的数据结构来实现的.首先输入一个字符串,表示一个表达式,然后用一个栈存储数字,另外一个栈存储符号 如果当前运算符优先级比栈顶元素优先级高,则入栈,若当前运算符优先级小于等于栈顶运算符优先级,则从数字栈中弹出两个元素,从符号栈中弹出一个符号,计算结果入栈(数字栈): 代码如下: #include<iostream> using namespace std; #include<cstring> #include<

【数据结构】栈的应用——中缀表达式求值(c++)

头文件: #pragma once #include <iostream> #include <assert.h> #include <string> using namespace std; template<class Type> class SeqStack { public: SeqStack(size_t sz = INIT_SZ); ~SeqStack(); public: bool empty()const; bool full()const;

中缀表达式求值模板

#include <bits/stdc++.h>using namespace std; /*判断符号间的优先关系函数*1表示>,0表示=,-1表示<*c1栈内的算符,c2栈外的算符*/int Judge(char c1,char c2){ int a1,a2; if('+'==c1||'-'==c1) a1 = 3; if('*'==c1||'/'==c1)a1 = 5; if('('==c1) a1 = 1; if(')'==c1) a1 = 7; if('#'==c1) a1

中缀表达式求值问题(栈的应用)

1 #include <iostream> 2 #include<stdlib.h> 3 4 using namespace std; 5 6 #define STACK_INIT_SIZE 100 7 #define STACKINCREASE 10 8 9 //为了简化函数,数据栈和符号栈用了一种结构体(虽然我知道可以用共用体解决问题,嫌烦), 10 //由此导致的后果是接下来所有的运算过程中不允许出现小数,而且由于是利用ASCII表来储存整数, 11 //所以要求运算数以及运

数据结构 栈的应用 --表达式求值

一: 中缀表达式求值  思想: 需要2个栈,运算对象栈OPND,运算符栈OPTR, 1:将栈OPND初始化为空,栈OPTR初始化为表达式的定界符# 2:扫描表达式,直到遇到结束符# 2.1:当前字符是运算对象,入栈OPND 2.2:当前字符是运算符且优先级比栈OPTR的栈顶运算符优先级高,入栈OPTR,处理下一个字符 2.3:当前字符是运算符且优先级比栈OPTR的栈顶运算符优先级低,则从栈OPND出栈2个运算对象,从栈OPTR出栈一个运算符进行运算,并将运算结果入栈OPND,处理当前字符 2.4

栈的应用——表达式求值

表达式求值是程序设计语言编译中的一个基本问题,它的实现就是对“栈”的典型应用.本文针对表达式求值使用的是最简单直观的算法“算符优先法”. 本文给出两种方式来实现表达式求值,方式一直接利用中缀表达式求值,需要用到两个栈,操作数栈和操作符栈.首先置操作数栈为空栈, 操作符栈仅有“#”一个元素.依次读入表达式中的每个字符,若是操作数则进操作数栈,若是操作符则和操作符栈的栈顶运算符比较优先权作相应操作,直至整个表达式求值完毕.方式二首先把中缀表达式转换为后缀表达式并存储起来,然后利用读出的后缀表达式完成