【算法】表达式求值--逆波兰算法介绍
逆波兰算法介绍
假定给定一个只 包含 加、减、乘、除,和括号的算术表达式,你怎么编写程序计算出其结果?
问题是:在表达式中,括号,以及括号的多层嵌套 的使用,运算符的优先级不同等因素,使得一个算术表达式在计算时,运算顺序往往因表达式的内容而定,不具规律性。 这样很难编写出统一的计算指令。
使用逆波兰算法可以轻松解决这个问题。他的核心思想是将普通的中缀表达式转换为后缀表达式。
什么是中缀表达式?例如a+b,运算符在两个操作数的中间。这是我们从小学开始学习数学就一直使用的表达式形式。
什么是后缀表达式?例如a b + ,运算符在两个操作数的后面。后缀表达式虽然看起来奇怪,不利于人阅读,但利于计算机处理。
转换为后缀表达式的好处是:
1、去除原来表达式中的括号,因为括号只指示运算顺序,不是实际参与计算的元素。
2、使得运算顺序有规律可寻,计算机能编写出代码完成计算。
算术表达式的组成部分
一个表达式有如下及部分元素组成
- 操作数:可以是任何数值:1,89 , 3.14159 ...
- 运算符:
分为单目运算符 如 sin , cos , 双目运算符 如 加、减、乘、除 。
运算符还会分为左结合性和右结合性。同级的左结合性的运算符从左往右依次计算。同级的右结合性的运算符从右往左依次计算。
如: 7-6+1 等价于 (7-6) + 1 ,因为普通加减运算符是左结合性的。
如:C语言中的组合赋值语句: a = b = 1 等价于 a = (b=1) ,因为赋值运算符在C中是右结合性的。
对于单目运算符,还分为前缀运算符和后缀运算符,如 sin(x) 是前缀运算符,而 阶乘运算符 : n ! 就是后缀运算符。
- 分界符:一般是圆括号 ( ) , 用于指示运算的先后顺序。
在本文中,只会考虑算术表达式 有 加、减、乘、除 运算符, 左右圆括号 ( , ) ,以及合法的数字简单的情况。对于更加复杂的运算符,只要对这个算法轻微修改,就可以支持。
逆波兰算法的原理
逆波兰算法的核心步骤就2个:
1、将中缀表达式转换为后缀表达式,例如输入的原始表达式是 3*(5+7) ,转换得到 3 5 7 + *
2、根据后缀表达式,按照特定的计算规则得到最终计算结果
下面详细介绍这个2步的操作。
中缀表达式转换为后缀表达式
你需要设定一个栈SOP,和一个线性表 L 。SOP用于临时存储运算符和左括号分界符( ,L用于存储后缀表达式。
遍历原始表达式中的每一个表达式元素
(1)如果是操作数,则直接追加到 L中。只有 运算符 或者 分界符( 才可以存放到 栈SOP中
(2)如果是分界符
Ⅰ 如果是左括号 ( , 则 直接压入SOP,等待下一个最近的 右括号 与之配对。
Ⅱ 如果是右括号),则说明有一对括号已经配对(在表达式输入无误的情况下)。不将它压栈,丢弃它,然后从SOP中出栈,得到元素e,将e依次追加到L里。一直循环,直到出栈元素e 是 左括号 ( ,同样丢弃他。
(3)如果是运算符(用op1表示)
Ⅰ如果SOP栈顶元素(用op2表示) 不是运算符,则二者没有可比性,则直接将此运算符op1压栈。 例如栈顶是左括号 ( ,或者栈为空。
Ⅱ 如果SOP栈顶元素(用op2表示) 是运算符 ,则比较op1和 op2的优先级。如果op1 > op2 ,则直接将此运算符op1压栈。
如果不满足op1 > op2,则将op2出栈,并追加到L,重复步骤3。
也就是说,如果在SOP栈中,有2个相邻的元素都是运算符,则他们必须满足:下层运算符的优先级一定小于上层元素的优先级,才能相邻。
最后,如果SOP中还有元素,则依次弹出追加到L后,就得到了后缀表达式。
伪代码:
#将参数中缀表达式expression转为后缀表达式存放在L中,返回L
function infixToSuffix(expression):
{
for each element in expression #对表达式中的每一个元素
{
if (element 是一个操作数)
{
L.append(element) #将这个元素追加到线性表L后
}
else if (element 是 一个运算符)
{
While (sop栈 不为空 && sop栈顶元素 是一个运算符 && element的优先级 <= sop栈顶运算符元素的优先级 )
{
L.append(sop.pop())
}
sop.push(element);
}
else if(element 是 一个分界符)
{
if (element is '(' )
{
sop.push(element)
}
else if( element is ')' )
{
While (sop栈不为空 && sop栈顶元素 不是 '(' ) #将匹配的2个括号之间的栈元素弹出,追加到L
{
L.append( sop.pop() );
}
if(sop栈不为空 )
{
sop.pop() #将匹配到的 '(' 弹出丢弃
}
}
}
}
While (sop 栈 不为空) #将sop栈中剩余的所有元素弹出,追加到L后
{
L.append(sop.pop())
}
return L
}
示例图:
根据后缀表达式计算得到最终结果
执行这步操作时,也需要一个栈scalc,用于存放计算中的操作数。
伪代码:
function suffixToResult(suffix_expression)
{
for each element in suffix_expression
{
if(element 是 操作数)
{
scalc.push(element)
}
e