栈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. 将结果入栈。

    重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果。

示例:

计算后缀表达式的值: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));    }}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纵横千里,捭阖四方

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值