一、问题描述

表达式求值是数学中的一个基本问题,也是程序设计中的一个简单问题。我们所涉及的表达式中包含数字和符号,本实验中处理的符号包括‘+’、‘-’、‘*’、‘/’、‘(’和‘)’,要求按照我们所习惯的计算顺序,正确计算出表达式的值,并输出至屏幕上。

本实验采用的数据结构是栈,表达式求值的问题用栈来实现是十分合适的。本实验用两个栈分别保存表达式中的数字和符号,以确定每对符号相遇时的优先级来决定当前应该进行什么操作。符号栈的操作分为三种:一是直接入栈;一是直接出栈;一是将当前栈顶符号出栈并计算,然后根据新的栈顶符号与当前符号的优先级关系重复操作类型的判断。

根据栈的特点以及实验操作的要求,本实验采用的是顺序栈实现。相比链栈来说,顺序栈的访问更方便,但其本质是没有差别的。

二、数据结构——栈

栈的本质是一个仅在队尾进行操作的线性表。相比于一般的线性表来说,栈的操作是受限制的。一般的栈仅有四个基本操作:初始化(InitStack())、取栈顶元素(GetTop())、入栈(Push())和出栈(Pop()),本实验多定义了一个操作函数ChangeTop(),用于更改栈顶元素的值,当然这个操作也等价于先出栈后入栈一个新的值。

栈的顺序表示类似于线性表的顺序表示,用一段地址连续的区域存储一个顺序栈,用物理存储位置的连表示栈的逻辑关系。与顺序表示的线性表类似,这也要求元素之间维持严格的物理位置关系,但正如上文所说,栈的操作都是限制在栈顶元素处,所以修改操作的有限突出了访问的便捷,因此栈的实现通常使用顺序表示而不是链式表示。

具体实现时,先申请一段连续的空间,当空间不够时,需要新申请一段更大的空间,把之前的所有数据移到新的位置。这样的操作确实十分耗时且无法避免,所以需要在实现之前更好地估计程序所需要的空间大小,减少此类操作的次数,同时最大化地利用空间。

三、算法的设计和实现

1、建立两个栈:一个是数字栈,一个是算符栈,首先向算符栈内添加一个符号‘#’。对于具体的符号栈的实现,可以通过编号的形式使用数字编号,也可以直接保存char类型的字符。

2、读取一个字符,自动滤掉空格、换行符等无关字符,当出现非法字符时结束程序并输出“表达式包含未被识别的符号。”

3、如果当前字符c是数字字符,即c满足条件“c>=’0’&&c<=’9’”,则继续读取,并由一个新的变量Num保存这个数字的真实值,具体实现是Num的初值为0,然后每次执行语句Num=Num*10+c-’0’,直到读取到非数字字符为止;如果当前字符c不是数字字符,调用函数OptrType(char c)得到该符号的编号。

4、当读到一个算符,考虑其入栈时,有三种情况:

(1)算符栈的栈顶元素优先级低于当前算符时,当前算符入栈;

(2)当两者优先级相同时,当前算符不入栈,算符栈的栈顶元素弹出;

(3)算符栈的栈顶元素优先级高于当前算符时,算符栈的栈顶元素弹出,数字栈弹出两个元素,按照顺序进行弹出的符号所对应的运算执行运算操作,将得到的结果压入数字栈。再将待入栈元素继续进行步骤4的判断。

5、当‘#’都弹出之后,计算结束。如果算式没有错误,则数字栈只剩一个元素,该元素就是算式的计算结果,输出即可。

6、清栈。

注:步骤4中提到的优先级是相对的,本实验中的实现方式是编号之后使用一个二维数组保存一个序对的关系的。比如Cmp[‘+’][‘-’]=1,而同样的有Cmp[‘-’][‘+’]=1。这样的规定方式是为了让四则运算中同级的运算能够先出现的先计算,从而避免了错误。具体关系见下表,其中值为-2的位置表示表达式有错误。

数组Cmp[][]的值
  + - * / ( ) #
+ 1 1 -1 -1 -1 1 1
- 1 1 -1 -1 -1 1 1
* 1 1 1 1 -1 1 1
/ 1 1 1 1 -1 1 1
( -1 -1 -1 -1 -1 0 -2
) 1 1 1 1 -2 1 1
# -1 -1 -1 -1 -1 -2 0

四、预期结果和实验中的问题

1、预期结果是程序可以正确计算出一个表达式的值,并判断出表达式的错误,包含“表达式中包含无法识别的字符”和“表达式输入错误”两种错误。比如输入的表达式为“2*(3+5)”,则能够得到的输出为“原式=16”;如果输入为“2x+3”,则输出为“表达式包含未被识别的符号”;如果输入为“1 1+1”,则输出为“表达式输出有误”。

2、实验中的问题及解决方案:

(1)map或者set解决算符映射问题

对于所出现的算符,我都编了一个数字号码以便访问。这样有一个弊端在于,当符号变多的时候,这样的没有特殊意义的数字编码映射显得十分累赘且不方便,即使每次访问一个专门编码、解码的函数,也显得很笨拙。

所以我想到的解决方法是,利用c++的STL标准库中的map或者set进行映射,这样在编码的时候可以直接以char类型的数据作为下标进行访问,十分方便。map和set这两个容器的原理都是基于红黑树,红黑树简单地说是一种二叉平衡查找树,具体维护操作这里不再赘述。

(2)template解决通用程序设计问题

本实验中涉及到的两个栈,一个是数字栈,一个是算符栈。正如上文所说,我将算符编号了,相当于说算符栈是一个维护int型元素的栈,而且其中的元素需要用作下标访问Cmp[][]数组以得到两个算符的优先级;而由于计算的必要,数字栈需要是一个维护double型元素的栈。一种暴力有效的方式是,建立两个几乎一模一样的栈,一个维护int型的元素,另一个维护double型的元素。当然我不愿意这样暴力,所以我使用了c++中的模板的程序设计,也就是template,简单地说这是c++程序设计语言中采用类型作为参数的程序设计,支持通用程序设计。于是这个问题就完美解决了。

(3)关于链栈

关于链栈的实现,我认为实现很容易。正如上文所说,栈可以看作一个有特殊操作限制的线性表,所以实现几乎与链栈无异。个人觉得栈这种数据结构没有必要使用链式表示,频繁的申请空间的时间且多出的链域空间都是不必要的浪费。

附:c++源代码:

  1 /*
  2 项目:顺序栈实现表达式求值
  3 作者:张译尹
  4 */
  5 #include <iostream>
  6 #include <cstdio>
  7
  8 using namespace std;
  9
 10 #define STACK_INIT_SIZE 100 //栈存储空间的初始分配量
 11 #define STACKINCREMENT 10 //栈存储空间的分配增量
 12
 13 #define MaxLen 120 //表达式长度
 14
 15 template <class T> class MyStack
 16 {
 17 private:
 18     T *base; //栈底指针,构造之前和销毁之后为NULL
 19     T *top; //栈顶指针
 20     int StackSize; //当前栈分配的空间大小
 21
 22 public:
 23     void InitStack() //构造一个空的栈
 24     {
 25         base = new T(STACK_INIT_SIZE);
 26         top = base;
 27         StackSize = STACK_INIT_SIZE;
 28     }
 29     void DestroyStack() //销毁栈
 30     {
 31         delete base;
 32         StackSize = 0;
 33     }
 34     void ClearStack() //清空栈
 35     {
 36         top = base;
 37     }
 38     bool StackEmpty() //判断栈空
 39     {
 40         return (base == top);
 41     }
 42     int StackLength() //返回当前栈内元素个数
 43     {
 44         return (top - base);
 45     }
 46     bool GetTop(T &Elem) //取栈顶元素,用Elem返回,失败返回true
 47     {
 48         if(StackEmpty())
 49             return true;
 50         Elem = *(top - 1);
 51         return false;
 52     }
 53     void Push(T Elem) //将Elem加入栈顶
 54     {
 55         int Len = StackLength();
 56         if(Len + 1 > StackSize) //当前栈的空间不够
 57         {
 58             T *NewBase = new T(StackSize + STACKINCREMENT);
 59             int i;
 60             for(i = 0; i < Len; i++)
 61                 NewBase[i] = *(base + i);
 62             delete base;
 63             base = NewBase;
 64             StackSize += STACKINCREMENT;
 65         }
 66         *top = Elem;
 67         top++;
 68     }
 69     bool Pop(T &Elem) //出栈栈顶元素,用Elem返回,失败时返回true
 70     {
 71         if(StackEmpty())
 72             return true;
 73         top--;
 74         Elem = *top;
 75         return false;
 76     }
 77     bool ChangeTop(T Elem) //将栈顶元素的值改为Elem,失败时返回true
 78     {
 79         if(StackEmpty())
 80             return true;
 81         *(top - 1) = Elem;
 82         return false;
 83     }
 84 };
 85
 86 int Cmp[10][10] = {{},
 87                    {0, 1,  1, -1, -1, -1,  1,  1},
 88                    {0, 1,  1, -1, -1, -1,  1,  1},
 89                    {0, 1,  1,  1,  1, -1,  1,  1},
 90                    {0, 1,  1,  1,  1, -1,  1,  1},
 91                    {0,-1, -1, -1, -1, -1,  0, -2},
 92                    {0, 1,  1,  1,  1, -2,  1,  1},
 93                    {0,-1, -1, -1, -1, -1, -2,  0}};
 94 //保存运算符之间的优先关系
 95
 96 void Check()
 97 {
 98     MyStack<int> s;
 99     s.InitStack();
100     int n;
101     int tmp;
102     scanf("%d", &n);
103     for(int i = 1; i <= n; i++)
104     {
105         scanf("%d", &tmp);
106         s.Push(tmp);
107     }
108     printf("Length = %d\n", s.StackLength());
109     printf("====================================\n");
110     while(!s.StackEmpty())
111     {
112         s.GetTop(tmp);
113         printf("%d\t", tmp);
114         s.Pop(tmp);
115         printf("%d\n", tmp);
116     }
117     printf("====================================\n");
118 }
119
120 inline int OptrType(char ch) //返回运算符编号
121 {
122     switch(ch)
123     {
124         case ' ': case '\n': return 0;
125         case '+': return 1;
126         case '-': return 2;
127         case '*': return 3;
128         case '/': return 4;
129         case '(': return 5;
130         case ')': return 6;
131         case '#': return 7;
132         case '.': return 8;
133     }
134     return -1;
135 }
136
137 double Cal(double x, double y, int Op) //x Op y
138 {
139     switch(Op)
140     {
141         case 1: return (x + y);
142         case 2: return (x - y);
143         case 3: return (x * y);
144         case 4: return (x / y);
145     }
146     return 0x3f;
147 }
148
149 void EvaluateExpression()
150 {
151     char c;
152     int cnt, t, tmp, pElem;
153     double x, y; //用于计算的数,x符号y
154     double Num;
155     bool OK = false; //表示算式是否完成计算,完成为true
156
157     //+:1 -:2 *:3 /:4 (:5 ):6 #:7
158     MyStack<int> Optr; //运算符栈
159     MyStack<double> Opnd; //运算数栈
160     Optr.InitStack();
161     Opnd.InitStack();
162
163     Optr.Push(7); //在起初压入'#'以便最后的清栈
164
165     printf("请输入一个算式,以\'#\'结束。\n");
166
167     while(c = getchar())
168     {
169         if((c >= '0' && c <= '9') || c == '.')
170         {
171             Num = 0.0;
172             while(c >= '0' && c <= '9')
173             {
174                 Num = Num * 10 + c - '0';
175                 c = getchar();
176             }
177             if(c == '.')
178             {
179                 cnt = 0;
180                 c = getchar();
181                 while(c >= '0' && c <= '9')
182                 {
183                     Num = Num * 10 + c - '0';
184                     c = getchar();
185                     cnt++;
186                 }
187                 while(cnt--)
188                     Num /= 10.0;
189             }
190             Opnd.Push(Num);
191         }
192         t = OptrType(c);
193         if(!t) //空格符、换行符
194             continue;
195         if(t == -1) //其他符号
196         {
197             printf("表达式包含未被识别的符号。\n");
198             return ;
199         }
200         for(;;)
201         {
202             Optr.GetTop(tmp);
203             if(Cmp[tmp][t] == -1)
204             {
205                 Optr.Push(t);
206                 break;
207             }
208             if(Cmp[tmp][t] == 0)
209             {
210                 if(t == 7) //'#'表示结束
211                 {
212                     Optr.Pop(pElem);
213                     OK = true;
214                 }
215                 else //遇到左右括号配对了
216                 {
217                     Optr.Pop(pElem);
218                 }
219                 break;
220             }
221             //Cmp[tmp][t] == 1
222             Optr.Pop(pElem);
223             Opnd.Pop(y);
224             Opnd.GetTop(x);
225             x = Cal(x, y, pElem);
226             Opnd.ChangeTop(x);
227             Optr.GetTop(tmp);
228         }
229         if(OK)
230             break;
231     }
232     tmp = Opnd.StackLength();
233     if(tmp != 1)
234     {
235         printf("表达式输入有误。\n");
236         return ;
237     }
238     Opnd.GetTop(x);
239     printf("原式 = %lf\n", x);
240 }
241
242 int main()
243 {
244     //Check();
245     EvaluateExpression();
246     return 0;
247 }

View Code

转载于:https://www.cnblogs.com/CQBZOIer-zyy/p/5185413.html

[数据结构]表达式求值相关推荐

  1. 如何利用计算机求函数解析式,数据结构表达式求值(计算器)实验报告(共10篇).doc...

    数据结构表达式求值(计算器)实验报告(共10篇) 数据结构表达式求值(计算器)实验报告(共10篇) 数据结构课程设计_实验报告(一)表达式求值(计算器) 数据结构课程设计 实验报告 起止时间:2015 ...

  2. 数据结构——表达式求值

    1.介绍 表达式分为中缀表达式,后缀表达式和前缀表达式. 前缀表达式又叫波兰式,而后缀表达式又叫逆波兰式,因为一开始研究这个的是个波兰数学家. 其中,我们最熟悉的就是中缀表达式.如下: (15-2)* ...

  3. 算术表达式求值的程序设计与实现_数据结构课程设计

    以下内容可且仅可供参考,如有错误欢迎指正. 部分思路借鉴算术表达式求值(C语言栈)_夜何其的博客-CSDN博客_c语言利用栈求解算术表达式侵删致歉 <算术表达式求值的程序设计与实现>题目要 ...

  4. python前缀表达式求值_python数据结构与算法 11 后缀表达式求值

    从本节开始,删除原版的英文,直接发译后的文稿. 后缀表达式求值 栈的最一个应用例子,计算一个后缀表达式的值.这个例子中仍然用栈的数据结构.不过,当扫描表达式的时候,这次是操作数压栈等待,不是转换算法中 ...

  5. c语言中缀表达式求值_数据结构考研笔记之栈与队列(四)栈与队列应用括号匹配、中缀表达式转前缀后缀问题...

    文字:独木 排版:独木 图片:独木 栈与队列 1.括号匹配问题 栈 例题1 例题2-----不匹配例题1 例题3-----不匹配例题2 2. 表达式求值问题 例题 1.中缀表达式转前缀表达式 2.中缀 ...

  6. linux算术表达式求值数据结构,数据结构:算数表达式求值演示

    题目:设计一个程序,演示用算符优先法对算数表达式求值的过程. 一.需求分析 以字符序列的形式从终端读入输入语法正确.不含变量的整数表达式.利用教科书表3.1给出的算符优先关系,实现对算数四则混合运算表 ...

  7. 数据结构—— 基于二叉树的算术表达式求值

    实验五 基于二叉树的算术表达式求值 数据结构--中序表达式求值(栈实现) 实验目的: 1.掌握二叉树的二叉链表存储表示和二叉树的遍历等基本算法. 2.掌握根据中缀表达式创建表达式树的算法 3.掌握基于 ...

  8. python 表达式求值数据结构_python 数据结构与算法

    python 数据结构与算法 1 python常见数据结构性能 1.1 List 1.1.1 安索引取值和赋值 1.1.2 列表append和__add__() 1.1.3 使用timeit模块测试执 ...

  9. java求值不用后缀表达式_数据结构之后缀表达式求值(java实现)

    数据结构之后缀表达式求值(java实现) 前记 ​ 今天在刷leet code的时候刷到了一道题,后缀表达式(逆波兰表达式)求值,我花了一会儿写了一下它的解法.但是今天我不谈什么是后缀表达式,有兴趣的 ...

最新文章

  1. BestCoder Round #92 比赛记录
  2. mysql数据库报Access denied for user 的解决方法
  3. [codevs 1743] 反转卡片
  4. intellij idea 部署项目的时候 图中application context 写不写有什么关系?有什么作用?...
  5. PHP模板引擎smarty详细介绍
  6. sql 上亿 查询_在文件上使用 SQL 查询的示例
  7. java分子分母题,JAVA黄金分割点练习题
  8. static 在C/C++中的用法总结
  9. Java软件工程师职位分析
  10. 日拱一卒,一路向前…… ——我的 CSDN 创作纪念日
  11. java获取虎牙直播弹幕消息_虎牙直播弹幕转换字幕格式 基于Node.js 的 huya-danmu
  12. 【渝粤教育】国家开放大学2018年春季 7403-22T素质与思想政治教育 参考试题
  13. 部署 php 项目,使用deployer 来部署PHP项目
  14. PO、VO、DO、TO、DTO、 BO、 QO、DAO、POJO
  15. 19254:狙击战(二分答案)
  16. 面试经验(互联网,研究所,国企)
  17. python实现局域网攻击软件_使用python的scapy库进行局域网内的断网攻击(基于ARP协议)...
  18. python下载文件进度图表_【python 】文件下载进度条(装逼利器)
  19. 2020年全国水土流失面积及水土流失综合治理情况分析[图]
  20. 划艇:dp/组合数/区间离散化

热门文章

  1. 60幅精美绝伦的绘景(Matte Paintings)作品欣赏(上篇)
  2. 转载 什么叫真学习 什么叫假学习
  3. 用U盘破除XP管理员密码
  4. NAC网络访问控制,你需要知道的!
  5. Python链接Mssql之Python库pymssql
  6. Kotlin Gson解析泛型对象
  7. 点击UITableView的cell展开收缩
  8. 苹果官方 Crash文件分析方法 (iOS系统Crash文件分析方法)
  9. 写在马哥教育第八期开始之前
  10. 算法导论一个让人很不爽的地方