题目
计算输入的字符串算式的结果,输入为一个字符串的算式,包含小括号、+、-、*、/以及小数点,0-9的整数,若输入的字符串是规范的算式(中缀式),则输出算式的计算结果,否则报错
示例
input: 1+((32.34+3)*4)-5
output:137.36
解决思路
表达式有前缀式、后缀式、中缀式,而我们一般提到的算式均为中缀式,中缀式表达式对于我们来说是很容易计算的,但是对于计算机来说,使用前缀式或者后缀式来计算是更加方便的,所以我们解决的完整思路如下:
- 判断算式是否符合规范的中缀式表达式
- 将中缀式转为前缀式或者后缀式
- 根据前缀式或者后缀式计算最终结果
判断算式
括号匹配
字符串算式中包含括号,所以需要判断括号是否匹配,判断括号匹配的步骤如下:- 初始化一个栈S,从左到右开始遍历字符串strs;
- 遇到左括号直接入栈S;
- 遇到右括号时:
- 若栈为空,则括号不匹配,则停止遍历;
- 若栈顶元素是左括号,则栈顶元素出栈,继续扫描下一个;
- 若栈顶元素为右括号,则括号不匹配,则停止遍历;
- 遇到数字或者运算符以及小数点时,不入栈
- 反复执行2-4,直到扫描完整个字符串
- 判断栈S是否为空,若栈为空,则表示括号匹配,否则括号不匹配
python代码实现如下:
1 | def isFormula(self, str): |
算式的匹配
目前想到最快的方式是利用正则表达式,简单分析下所有算式的共性:
- 以括号或者数字开头
- 以括号或者数字结尾
- 中间有1个运算符或者n个(运算符+数字)的表达式组成
- 每个数字前均有一个或者n个括号
- 每个数字中可能有一个(小数点+数字)组成分数
根据以上规则,得到的正则表达式如下:
1 | pattern = r'^\(*\d+(\.\d+)?((\+|\*|/|-)\(*\d+(\.\d+)?\)*)*(\+|\*|/|-)\d+(\.\d+)?\)*$' |
PS:当然,括号匹配也应该可以加到正则表达式中,但是我并没有找到,如果有读者想到的话,可以留言交流
所以判断算式是否匹配的代码如下:
1 | pattern = r'^\(*\d+(\.\d+)?((\+|\*|/|-)\(*\d+(\.\d+)?\)*)*(\+|\*|/|-)\d+(\.\d+)?\)*$' |
再结合括号匹配的结果,则可以得到算式是否符合规范
中缀式转前缀式
简介
前缀式又称为逆波兰式,前缀式的表达式位于操作数之前,如下:
‘+’, 1, ‘-‘, ‘*’, ‘+’, 32.34, 3, 4, 5
前缀式表达式求值
前缀式求值过程如下:
- 从右至左扫描算式
- 遇到数字时,将数字压入堆栈,
- 遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素在前,次顶元素在后),并将计算结果入栈;
- 重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果。
中缀式转前缀式
- 初始化两个栈:运算符栈s1,储存中间结果的栈s2,记录连续数个数的inflag=0,以及记录是否出现过小数的decimalflag=0
- 从右至左扫描中缀表达式
- 遇到操作数时,
3.1.若intflag和decimalflag均为0时,则直接入栈,intflag++
3.2.若intflag=0,decimalflag=1,则s2出栈后加上当前操作数后再入栈
3.3.若intflag>0,则s2出栈后加上当前数字左移intflag位的值(即当前数乘以10的intflag次方)- 遇到运算符时,比较其与s1栈顶运算符的优先级,intflag和decimalflag置为0
4.1.如果s1为空,或栈顶运算符为右括号“)”,则直接将此运算符入栈
4.2.否则,若优先级比栈顶运算符的较高或相等,也将运算符压入s1
4.3.否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较- 遇到括号时
5.1.如果是右括号“)”,则直接压入s1
5.2.如果是左括号“(”,则依次弹出S1栈顶的运算符,并压入S2,直到遇到右括号为止,此时将这一对括号丢弃- 遇到小数点时,将decimalflag置为1,并将当前栈顶的数字右移intflag位后入栈
- 重复步骤2至5,直到表达式的最左边
- 将s1中剩余的运算符依次弹出并压入s2
- 依次弹出s2中的元素并输出,结果即为中缀表达式对应的前缀表达式
中缀式转后缀式
后缀式求值
- 从左向右扫描算式
- 遇到数字时,将数字压入堆栈,
- 遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素在后,次顶元素在前),并将计算结果入栈;
- 重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果。
中缀式转后缀式
- 初始化两个栈:运算符栈s1和储存中间结果的栈s2,记录连续数个数的inflag=0,以及记录是否出现过小数的decimalflag=0
- 从左至右扫描中缀表达式;
- 遇到操作数时,
3.1.若intflag和decimalflag均为0时,则直接入栈s2,intflag++
3.2.若intflag=0,则将当前数入栈
3.3.若intflag>0并且decimalflag=0,则将s2栈顶元素左移一位加上当前数后入栈
3.4.若intflag>0并且decimalflag=1,则将s2栈顶元素出栈后加上当前数右移intflag位后入栈- 遇到运算符时,比较其与s1栈顶运算符的优先级:
4.1如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
4.2否则,若优先级比栈顶运算符的高,也将运算符压入s1(注意转换为前缀表达式时是优先级较高或相同,而这里则不包括相同的情况);
4.3否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较;- 遇到括号时:
5.1如果是左括号“(”,则直接压入s1;
5.2如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃;
5.3重复步骤2至5,直到表达式的最右边;- 遇到小数点时,记录decimalflag=1;
- 将s1中剩余的运算符依次弹出并压入s2;
- 依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式
python实现
中缀式转前缀式
1 | def Infix2Prefix(self, infixstrs): |
中缀式转后缀式
1 | def Infix2Suffix(self, infixstrs): |
算式的计算
1 | def calculate(self, strs, is_Prefix=True): |