栈5:前中后缀表达式
Posted 纵横千里,捭阖四方
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了栈5:前中后缀表达式相关的知识,希望对你有一定的参考价值。
前面我们分析的几个计算器的题目都是最后返回一个结果值就行了,但是在很多场景下,我们只是处理表达式而不能仅仅做运算,例如字符串处理的时候,我们只是根据需要进行调整,输出仍然是一个表达式串。这种情况在各类计算机语言的编译、自然语言处理等场景都大量用到,为此还有专门的名字:前缀表达式,中缀表达式和后缀表达式。
这几种表达式的处理更适合在后面的树专题中分析,但是我们后面要整理的关于树的内容已经非常多了。所以我们这里就了解一下。
https://www.cnblogs.com/menglong1108/p/11619896.html
1.三种表达式的概念
前缀、中缀、后缀表达式是对表达式的不同记法,其区别在于运算符相对于操作数的位置不同,前缀表达式的运算符位于操作数之前,中缀和后缀同理。如下图,其实这就对应了树的前中后三种遍历方式。
对应的三种表达式就是:
中缀表达式:1 + (2 + 3) × 4 - 5
前缀表达式:- + 1 × + 2 3 4 5
后缀表达式:1 2 3 + 4 × + 5 -
从上面的例子我们也可以看到 中缀表达式是最像人话的,它是一种通用的算术或逻辑公式表示方法,操作符以中缀形式处于操作数的中间。
虽然人的大脑很容易理解与分析中缀表达式,但对计算机来说中缀表达式却是很复杂的,因此计算表达式的值时,通常需要先将中缀表达式转换为前缀或后缀表达式再进行求值。
(1)前缀表达式
前缀表达式的运算符位于两个相应操作数之前,前缀表达式又被称为前缀记法或波兰式。
前缀表达式的计算机求值过程是从右至左扫描表达式。遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算,并将结果入栈。重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果
示例:
计算前缀表达式的值:- + 1 × + 2 3 4 5
1)从右至左扫描,将5,4,3,2压入堆栈;
2)遇到+运算符,弹出2和3(2为栈顶元素,3为次顶元素),计算2+3的值,得到5,将5压入栈;
3)遇到×运算符,弹出5和4,计算5×4的值,得到20,将20压入栈;
4)遇到1,将1压入栈;
5)遇到+运算符,弹出1和20,计算1+20的值,得到21,将21压入栈;
6)遇到-运算符,弹出21和5,计算21-5的值,得到16为最终结果
可以看到,用计算机计算前缀表达式是非常容易的,不像计算后缀表达式需要使用正则匹配。
(2)后缀表达式
后缀表达式与前缀表达式类似,只是运算符位于两个相应操作数之后,后缀表达式也被称为后缀记法或逆波兰式。
后缀表达式的计算机求值与前缀表达式类似,只是顺序是从左至右:
-
从左至右扫描表达式,遇到数字时,将数字压入堆栈;
-
遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算;
-
将结果入栈。
重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果。
示例:
计算后缀表达式的值:1 2 3 + 4 × + 5 -
1)从左至右扫描,将1,2,3压入栈;
2)遇到+运算符,3和2弹出,计算2+3的值,得到5,将5压入栈;
3)遇到4,将4压入栈
4)遇到×运算符,弹出4和5,计算5×4的值,得到20,将20压入栈;
5)遇到+运算符,弹出20和1,计算1+20的值,得到21,将21压入栈;
6)遇到5,将5压入栈;
7)遇到-运算符,弹出5和21,计算21-5的值,得到16为最终结果
(3)中缀表达式转化为前缀和后缀表达式的转化步骤
因为中缀表达式是给人看的,而前缀和后缀是给计算机看的,所以必然存在转换关系,中缀转成其他两种的步骤是:
按照运算符的优先级对所有的运算单位加括号,将运算符移动到对应括号的前面(前缀表达式)或后面(后缀表达式)。去掉括号,得到前缀或后缀表达式
示例:
中缀表达式:1+(2+3)×4-5
1)加括号
式子变成 ((1+((2+3)×4))-5)
2)移动运算符
对于前缀表达式,变成了 -(+(1×(+(23)4))5)
对于后缀表达式:变成了((1((23)+4)×)+5)-
3)去掉括号
前缀表达式:- + 1 × + 2 3 4 5
后缀表达式:1 2 3 + 4 × + 5 -
2.中缀表达式转换为后缀表达式
中缀转后缀的转换过程需要用到栈,这里我们假设栈A用于协助转换,并使用数组B用于存放转化后的后缀表达式具体过程如下:
1)如果遇到操作数,我们就直接将其放入数组B中。
2)如果遇到运算符,则我们将其放入到栈A中,遇到左括号时我们也将其放入栈A中。
3)如果遇到一个右括号,则将栈元素弹出,将弹出的运算符输出并存入数组B中直到遇到左括号为止。注意,左括号只弹出并不存入数组。
4)如果遇到任何其他的操作符,如(“+”, “*”,“(”)等,从栈中弹出元素存入数组B直到遇到发现更低优先级的元素(或者栈为空)为止。弹出完这些元素后,才将遇到的操作符压入到栈中。有一点需要注意,只有在遇到” ) “的情况下我们才弹出” ( “,其他情况我们都不会弹出” ( “。
5)如果我们读到了输入的末尾,则将栈中所有元素依次弹出存入到数组B中。
6)到此中缀表达式转化为后缀表达式完成,数组存储的元素顺序就代表转化后的后缀表达式。
执行图示过程如下:
最后输出
1 3 9 2 - * 9 +
简单分析一下流程,建议你画画图自己写一下, 如果你能自己想明白就不用看下面这一大段话了:
当遇到操作数时(规则1),直接存入数组B中,当i=1(规则2)时,此时运算符为+,直接入栈,当i=3(规则2)再遇到运算符*,由于栈内的运算符+优先级比*低,因此直接入栈,当i=4时,遇到运算符’(‘,直接入栈,当i=6时,遇运算符-,直接入栈,当i=8时(规则3),遇’)’,-和’(‘直接出栈,其中运算符-存入后缀数组B中,当i=9时(规则5),由于*优先级比+高,而+与+平级,因此和+出栈,存入数组B,而后面的+再入栈,当i=10(规则5),结束,+直接出栈存入数组B,此时数组B的元素顺序即为1 3 9 2 - * + 9 +,这就是中缀转后缀的过程。
接着转成后缀后,我们来看看计算机如何利用后缀表达式进行结果运算,通过前面的分析可知,后缀表达式是没有括号的,而且计算过程是按照从左到右依次进行的,因此在后缀表达的求值过程中,当遇到运算符时,只需要取前两个操作数直接进行计算即可,而当遇到操作数时不能立即进行求值计算,此时必须先把操作数保存等待获取到运算符时再进行计算,如果存在多个操作数,其运算次序是后出现的操作数先进行运算,也就是后进先运算,因此后缀表达式的计算过程我们也需要借助栈来完成,该栈用于存放操作数,后缀表达式的计算过程及其图解如下:
借助栈的程序计算过程:
参考实现:
public class CalculateExpression {
/**
* 中缀转后缀
* @param expstr 中缀表达式字符串
* @return
*/
public static String toPostfix(String expstr)
{
//创建栈,用于存储运算符
SeqStack<String> stack = new SeqStack<>(expstr.length());
String postfix="";//存储后缀表达式的字符串
int i=0;
while (i<expstr.length())
{
char ch=expstr.charAt(i);
switch (ch)
{
case '+':
case '-':
//当栈不为空或者栈顶元素不是左括号时,直接出栈,因此此时只有可能是*/+-四种运算符(根据规则4),否则入栈
while (!stack.isEmpty() && !stack.peek().equals("(")) {
postfix += stack.pop();
}
//入栈
stack.push(ch+"");
i++;
break;
case '*':
case '/':
//遇到运算符*/
while (!stack.isEmpty() && (stack.peek().equals("*") || stack.peek().equals("/"))) {
postfix += stack.pop();
}
stack.push(ch+"");
i++;
break;
case '(':
//左括号直接入栈
stack.push(ch+"");
i++;
break;
case ')':
//遇到右括号(规则3)
String out = stack.pop();
while (out!=null && !out.equals("("))
{
postfix += out;
out = stack.pop();
}
i++;
break;
default:
//操作数直接入栈
while (ch>='0' && ch<='9')
{
postfix += ch;
i++;
if (i<expstr.length())
ch=expstr.charAt(i);
else
ch='=';
}
//分隔符
postfix += " ";
break;
}
}
//最后把所有运算符出栈(规则5)
while (!stack.isEmpty())
postfix += stack.pop();
return postfix;
}
/**
* 计算后缀表达式的值
* @param postfix 传入后缀表达式
* @return
*/
public static int calculatePostfixValue(String postfix)
{
//栈用于存储操作数,协助运算
LinkedStack<Integer> stack = new LinkedStack<>();
int i=0, result=0;
while (i<postfix.length())
{
char ch=postfix.charAt(i);
if (ch>='0' && ch<='9')
{
result=0;
while (ch!=' ')
{
//将整数字符转为整数值ch=90
result = result*10 + Integer.parseInt(ch+"");
i++;
ch = postfix.charAt(i);
}
i++;
stack.push(result);//操作数入栈
}
else
{ //ch 是运算符,出栈栈顶的前两个元素
int y= stack.pop();
int x= stack.pop();
switch (ch)
{ //根据情况进行计算
case '+': result=x+y; break;
case '-': result=x-y; break;
case '*': result=x*y; break;
case '/': result=x/y; break; //注意这里并没去判断除数是否为0的情况
}
//将运算结果入栈
stack.push(result);
i++;
}
}
//将最后的结果出栈并返回
return stack.pop();
}
//测试
public static void main(String args[])
{
String expstr="1+3*(9-2)+90";
String postfix = toPostfix(expstr);
System.out.println("中缀表达式->expstr= "+expstr);
System.out.println("后缀表达式->postfix= "+postfix);
System.out.println("计算结果->value= "+calculatePostfixValue(postfix));
}
}
3.leetcode150 逆波兰表达式
根据 逆波兰表示法,求表达式的值。
有效的算符包括 +
、-
、*
、/
。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
说明:
整数除法只保留整数部分。
给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
示例:
示例1.
输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例2.输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例3:
输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
输出:22
解释:
该算式转化为常见的中缀算术表达式为:
((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22
逆波兰表达式严格遵循「从左到右」的运算。计算逆波兰表达式的值时,使用一个栈存储操作数,从左到右遍历逆波兰表达式,进行如下操作:
-
如果遇到操作数,则将操作数入栈;
-
如果遇到运算符,则将两个操作数出栈,其中先出栈的是右操作数,后出栈的是左操作数,使用运算符对两个操作数进行运算,将运算得到的新操作数入栈。
整个逆波兰表达式遍历完毕之后,栈内只有一个元素,该元素即为逆波兰表达式的值。
代码:
class Solution {
public int evalRPN(String[] tokens) {
Deque<Integer> stack = new LinkedList<Integer>();
int n = tokens.length;
for (int i = 0; i < n; i++) {
String token = tokens[i];
if (isNumber(token)) {
stack.push(Integer.parseInt(token));
} else {
int num2 = stack.pop();
int num1 = stack.pop();
switch (token) {
case "+":
stack.push(num1 + num2);
break;
case "-":
stack.push(num1 - num2);
break;
case "*":
stack.push(num1 * num2);
break;
case "/":
stack.push(num1 / num2);
break;
default:
}
}
}
return stack.pop();
}
public boolean isNumber(String token) {
return !("+".equals(token) || "-".equals(token) || "*".equals(token) || "/".equals(token));
}
}
以上是关于栈5:前中后缀表达式的主要内容,如果未能解决你的问题,请参考以下文章