中缀表达式转后缀表达式及运算的程序设计(计算器设计思想)

中缀表达式转后缀表达式及运算的程序设计
如果操作符的元数(arity)是固定的,则语法上不需要括号仍然能被无歧义地解析。波兰记法是波兰数学家扬・武卡谢维奇1920年代引入的,用于简化命题逻辑。
阿隆佐・邱奇在他的经典著作《数理逻辑》中提出该表达方法是一种值得被关注的记法系统,甚至将它与阿弗烈・诺夫・怀海德和伯特兰・罗素在《数学原理》中的逻辑表达式相提并论。波兰表示法虽然在逻辑领域没有被广泛使用,但仍在计算机科学领域占有一席之地。基于堆栈的操作符由于其本身的特性,无需括号也很容易区分运算的顺序,因此大量使用波兰记法。运算波兰表达式时,无需记住运算的层次,只需要直接寻找第一个运算的操作符。需注意的是,当运算时,操作符是作用在第一个操作数上,特别是需注意不满足交换律的运算,如除法、减法。本文只总结用c++语言实现例如表达式:“23+(66/(2+3)+7)23” 逆波兰表示法及运算的思路,数学原理需参考相关文献。
一、字符串的词法语法分析
从输入设备接收字符串形式的表达式(2
3+(66/(2+3)+7)23),首先对该字符串进行词法分析。
1、首先规定四则运算表达式由数字及小数点 ( . )、(+ - * /)四个运算符、和分割符“( )”组成,不函空格等其他字符,四则运算表达式输入以 “;”结束(如有需要可扩展开方、幂运算等特殊运算符,此文只以加减乘除为例);
2、首先对字符串(2
3+(66/(2+3)+7)23)去空格处理、在去空格后检测输入字符的合法性(合法字符:0、1、2、3、4、5、6、7、8、9、+、-、、/、( 、)、. );
3、括号匹配检查;
4、负号检查(-号在字符串开头、左括号( 右侧、运算符右侧链接数字,则认为该符号为负号);
5、检查数字与操作符是否匹配(数字个数> 操作符个数 + 1);
6、检查操作符两侧合法性,操作符左侧只能为数字或右括号,操作符右侧只能为数字左括号;
7、检查小括号左右侧合法性,左括号左侧只能为空或者是操作符,左括号右侧只能是数字;
右括号左侧只能是数字,右括号右侧只能是操作符或者为空;
8、更严谨的语法检测。(此程序可作通用计算器使用)
三、优先级分析
1、运算符优先级从低到高依次为(“(、)”)、(“+、-”)、(“
、/”),优先级相同时按从左至右的运算原则;
2、特别注意除法、减法等不满足交换率的运算符,“、+” 法适用交换原则,“/、-” 法不适用交换原则。
四、数据结构选型
堆栈结构、本质是树的中序遍历序列转后续遍历。
案例表达式:1
3+(66/(2+3)+7)*23,转换成树为下图

五、程序流程演示图
表达式转逆波兰表达式的原则
1、按顺序逐个读取表达式序列中的元素;
2、当扫描到的元素为数字时,将数字放入容器link中;
当扫描到运算符时,运算符入栈,入栈时判断该运算符与栈顶运算符的优先级,当栈顶运算符优先级低于该元素优先级时该运算符直接入栈;当栈为空时直接入栈;当栈顶元素为左括号时直接入栈;否则为栈顶运算符优先级大于该元素的情况,此时栈顶元素弹出,放入容器link中,然后继续比较,直到栈顶元素优先级低于该元素;此程序中规定栈顶元素优先级与要入栈的元素优先级相同时,该元素执行入栈操作。(出栈操作时判断空栈情况避免异常)
3、当扫描到左括号时直接入栈,当扫描到右括号时,栈内元素依次弹出,依次放入容器link中,指导匹配的左括号弹出为止。
4、当扫描到表达式序列结尾时,弹出栈内所有元素,依次放入容器link中。
示例表达式利用栈操作转成逆波兰表达式步骤示意图如下:
1、开始扫描第一个元素,建立一个栈stack,和动态数组容器link;

2、第一个元素为数字,直接放入容器link中;

3、第二个元素为 * 号,且栈为空,直接入栈。

4、第三个元素为数字,直接放入容器link中;

5、第四个元素为 + 号,与栈顶元素*比较优先级+号优先级较低

6、从栈中弹出号,将号放入容器link中,+ 号入栈;

7、扫描到左括号,左括号直接入栈;

8、扫描到数字66,66直接放入容器link中;

9、扫描到左/号,/ 号优先级比( 左括号高,直接入栈;

10、又扫描到一个左括号,左括号直接入栈;

11、扫描到数字2,数字2直接放到容器link中;

12、扫描到+号,+号优先级比栈顶左括号高,直接入栈;

14、扫描数字3,直接放入容器link中;

15、扫描到 ) 右括号,将栈内匹配的( 左括号之间所有元素依次弹出,放入容器link中;

16、扫描到 + ,与栈顶元素 / 号比较,/优先级较高,/出栈放入容器中,+入栈

17、扫描到数字7 ,直接放入容器中

18、扫描到右括号,将匹配的左括号之间的元素弹出,放入容器link中;

19、扫描到 * ,直接入栈;

20、扫描到最后一个元素23,数字直接放入容器,栈内元素依次弹出放入容器,逆波兰表达式为:2 3 * 66 2 3 + / 7 + 23 * +

示例逆波兰表达式利用栈操作计算结果
计算原则:依次扫描逆波兰表达式,当遇到数字时入栈;当遇到运算符时,取出栈顶相邻两个操作数运算,运算结果入栈;当扫描结束栈内只有一个元素时,该元素为计算结果。
步骤示意图如下:
1、前三个元素依次为两个数字2、3 和一个操作符* ,按照原则数字先入栈,然后碰到运算符取出数字及运算符进行运算,结果入栈;

2、依次扫描到三个数字 66 、 2、 3,三个数字入栈,当扫描到 + 号时,执行2 + 3 操作,结果入栈;

3、当扫描到 / 号时,从栈顶取出相邻两个元素计算,结果入栈。此时注意 / 左右操作数;

4、遇到加号继续计算

5、最终结果为栈内元素 470.6

六、基于c++11 特性的程序设计。
检查合法的待处理字符串string 类型
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
1 * 3 + ( 6 6 / ( 2 + 3 ) + 7 ) * 2 3

类关系图如下:(只阐述思想)

五个类的具体方法如下:
1、接口类
class opcode //接口函数
{
public:
virtual ~opcode() {};
virtual char*& getcode() = 0; //获取编码
virtual unsigned int getposion() = 0; //获取编码在串中位置
virtual unsigned int getprilevel() = 0; //获取优先级
virtual unsigned int getcodelen() = 0; //获取编码在串中长度,用于挖字符串
virtual double getcodevalue() = 0; //计算值
virtual char getflag() = 0;
virtual int setprilevel(int i = 0) =0; //获取优先级
virtual int setflag(char ch = ‘#’) = 0; //标识位,用于扩展程序
virtual opcode*& getright() = 0; //右孩子,用于扩展程序,将表达式存树结构
virtual void setright(opcode*& opc) = 0;
virtual opcode*& getleft() = 0; //左孩子
virtual void setleft(opcode*& opc) = 0;
};

2、操作数类,用于封装操作数
class opnumber :public opcode //操作数函数
{
private:
char* numcode; //操作数字符串形式
double numvalue; //操作数的值
unsigned int posion; //操作数在串中的位置,此程序中可省略
unsigned int numcodelen; //串中一个数字占的长度
unsigned int prilevel; //优先级
char flag; //标识位
opcode* right; //左孩子
opcode* left;
public:
opnumber(const char* numcode = NULL, unsigned int posion = 0); //构造函数
opnumber(const opnumber& opnum); //拷贝构造函数
virtual ~opnumber(); //析构函数
};

3、运算类,用于封装操作数
class opsign : public opcode
{
private:
char* signcode;
unsigned int posion;
unsigned int signcodelen;
unsigned int prilevel;
char flag;
opcode* right;
opcode* left;
public:
opsign(const char* signcode = NULL, unsigned int posion = 0);
opsign(const opsign& opsig);
virtual ~opsign();

};

4、数据封装成容器中间类
class checkcode
{
private:
vector<opcode*> vop;
char
incode;
public:
checkcode(const char* incode);
~checkcode();
int checkLegalinput(); //检查输入合法性
int checkbracketmatch(); //检查括号匹配
int checknegative(); //处理负号,待处理
vector<opcode*>& packagecode(); //打包成动态数组对象
void printcode();
};

5、核心算法类
class codeRPN
{
private:
vector<opcode*> vcop; //打包好的动态数组类对象
vector<opcode*> vcoR; //堆栈式逆波兰表达式
opcode* rootnode; //二叉树式逆波兰表达式
public:
codeRPN(vector<opcode*>& vc);
~codeRPN();
double calculatedRPN(); //计算逆波兰表达式
vector<opcode*>& turntoRPN(); //转换成逆波兰表达式
void printvcoR(); //打印测试函数
};

七、完成代码后结果如下

八、注意事项
1、训练栈的运用,充分利用栈先进后出的特性存储中间结果;
2、利用c++ 基于继承的多态特性,将不同的数据一起处理,如放入同一个堆栈中。
3、充分利用面向对象的编程思想,使程序逻辑简单易懂。首先将输入的表达式字符串分类封装。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值