表达式求值

  • 中缀表达式求值
  • 中缀表达式转后缀表达式
  • 后缀表达式求值(逆波兰表达式求值)
  • 前缀表达式(波兰表达式)


首先,大家可能不知道前缀表达式和中缀表达式,后缀表达式是什么,其有什么区别呢。我先简单介绍一下:

前缀表达式是一种没有括号的算术表达式,与中缀表达式不同的是,其将运算符写在前面,操作数写在后面。为纪念其发明者波兰数学家Jan Lukasiewicz,前缀表达式也称为“波兰式”。例如,- 1 + 2 3,它等价于1-(2+3)

中缀表达式及我们正常的式子eg:1-(2+3)

后缀表达式: 逆波兰式(Reverse Polish notation,RPN,或逆波兰记法),也叫后缀表达式(将运算符写在操作数之后)eg:1-(2+3) 1 2 3+ -

中缀表达式求值

中缀表达式即我们正常的表达式,我们先讲一下其求值规则:

  • 1.通过一个index值(索引),来完成我们的表达式
  • 2.如果我们发现是一个数字,就直接入数栈
  • 3.如果我们发现是一个符号,分一下情况
  • 3.1.如果发现当前的符号栈为空,就直接入栈
  • 3.2.如果符号栈中有操作符,就进行比较,如果当前操作符的优先级小于或者等于栈中的操作符,就需要从数栈中pop出一个符号,进行运算,将得到的结果入数栈,然后将当前的操作符入符号栈,如果当前操作符的优先级大于栈中的操作符,就直接入符号栈。如果有括号 先将左括号入栈 碰到右括号的时候,将符号栈中栈顶元素出栈,数字栈中两次出栈顶元素,进行运算,将运算的结果插入数字栈中, 直到为右括号便停止该循坏 并移除符号栈中的左括号
  • 4.当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并进行运算。
  • 5.最后在数栈只有一个数字,就是表达式的结果

首先我们来模拟一下:

比如:(3+4)*5-2

javascript 前缀匹配 startwith 前缀表达式求值java_数据结构

javascript 前缀匹配 startwith 前缀表达式求值java_java_02

javascript 前缀匹配 startwith 前缀表达式求值java_运算符_03

至此步骤结束,大家可以自己多模拟几遍。
代码:
首先我模拟了一个栈,里面添加了关于优先级的判断和是否为运算符号的判断,大家可以以只看这两种方法即可。

class ArrayStack{
    private int maxSize;//栈的大小
    private int[] stack;//数组,存放数据
    private int top=-1;//top表示栈顶,初始化为-1
    public ArrayStack(int maxSize){
        this.maxSize=maxSize;
        stack=new int[maxSize];
    }
    //栈满
    public boolean isFull(){
        return top==maxSize-1;
    }
    //栈空
    public boolean isEmpty(){
        return top==-1;
    }
    //入栈
    public void push(int data){
        if(isFull()){
            System.out.println("栈满,插入失败");
            return;
        }
        stack[++top]=data;
    }
    //出栈
    public int pop(){
        if(isEmpty()){
            System.out.println("栈空,无元素");
        return -1;
        }
        int data=stack[top];
        top--;
        return data;
    }
    //获取顶端元素
    public int peek(){
        return stack[top];
    }
    //显示栈的情况(遍历栈,从上往下)
    public void list(){
        if(isEmpty()){
            System.out.println("栈空");
            return;
        }
        for (int i = top; i >=0; i--) {
            System.out.print(stack[i]+" ");
        }
    }

    //用于判断优先级  在进行表达式运算时需要用到
    public int priority(int oper){
        if(oper=='*'||oper=='/'){
            return 1;//优先级最高
        }
        else if(oper=='+'||oper=='-'){
            return 0;
        }
        else return -1;
    }
    //判断是不是一个运算符
    public boolean isOper(char val){
        return val=='+'||val=='-'||val=='*'||val=='/'||val=='('||val==')';
    }
    //计算方法
    public int cal(int num1,int num2,int oper){
        int res=0;
        switch (oper){
            case '+':
                res=num1+num2;
                break;
            case '-':
                res=num2-num1;
                break;
            case '*':
                res=num1*num2;
                break;
            case '/':
                res=num2/num1;
                break;
             default:break;
        }
        return res;
    }
}
public class ComputerDemo {
    public static void compute(String expression){
        ArrayStack numStack=new ArrayStack(10);
        ArrayStack operStack=new ArrayStack(10);
        int index=0;
        int num1=0;//表示输出数字的栈顶元素
        int num2=0;//表示地如此输出数字栈的栈顶元素
        int oper=0;//运算符号  符号栈的栈顶元素
        int res=0;//计算结果
        char ch=' ';//将每次扫描得到的char保存到ch
        String keepNum="";//防止不止一个数字 为多位数
        //开始while循环的扫描expression
        while (index!=expression.length()) {
            //依次得到expression的每一个字符
            ch = expression.substring(index, index + 1).charAt(0);
            //判断ch是什么,然后做相应的处理
            if(operStack.isOper(ch)){
                /**
                 *  3.1.如果发现当前的符号栈为空,就直接入栈
                 *  3.2.如果符号栈中有操作符,就进行比较,如果当前操作符的
                 *    优先级小于或者等于栈中的操作符,就需要从数栈中pop出一个符号,
                 *    进行运算,将得到的结果入数栈,然后将当前的操作符入符号栈,如果当前操作符
                 *    的优先级大于栈中的操作符,就直接入符号栈
                 *
                 *    如果有括号 先将左括号入栈 碰到右括号的时候,将符号栈中栈顶元素出栈,数字栈中两次出栈顶元素,进行运算,
                 *    将运算的结果插入数字栈中,直到为右括号便停止该循坏 并移除符号栈中的左括号
                 */
                if(ch=='('){
                    operStack.push(ch);
                }
                else if(ch==')'){
                    //获取符号并进行运算 进行消括号操作
                    while (operStack.peek()!='(') {
                        oper=operStack.pop();
                        num1 = numStack.pop();
                        num2 = numStack.pop();
                        res = numStack.cal(num1, num2, oper);
                        //把运算结果入数栈
                        numStack.push(res);
                    }
                    operStack.pop();//去掉左括号
                }
            else if (!operStack.isEmpty()) {
                //判断当前符号栈是否为空
                if (operStack.priority(ch) <= operStack.priority(operStack.peek())) {
                    num1 = numStack.pop();
                    num2 = numStack.pop();
                    oper = operStack.pop();
                    res = numStack.cal(num1, num2, oper);
                    //把运算结果入数栈
                    numStack.push(res);
                    //然后将当前的操作符入符号栈
                    operStack.push(ch);
                }
            else {
                    //如果当前的操作符的优先级大于栈中的操作符,就直接入符号栈
                    operStack.push(ch);
                }
            }else {
                //如果为空则直接入符号栈
                operStack.push(ch);
            }
        }
        else {//如果是数,则直接入数栈
                keepNum+=ch;
                //如果ch已经是最后一位,则直接入栈
                if(index==expression.length()-1){
                    numStack.push(Integer.parseInt(keepNum));
                }else {
                    //判断下一位是不是数字,如果是数字,就继续扫描,如果是运算符则入栈
                    //注意看后一位,不是index++
                    if(operStack.isOper(expression.substring(index+1,index+2).charAt(0))){
                      //如果后一位是运算符,则入栈keepNum="1" "123"
                      numStack.push(Integer.parseInt(keepNum));
                      // keepNum清空
                        keepNum="";
                    }
                }
            }
            //让index+1,并判断是否扫描到expression最后
            index++;
    }
    //当表达式扫描完毕,就顺序的从数栈和符号栈中pop出相应的数和符号,并运行
        while (!operStack.isEmpty()){
            num1=numStack.pop();
            num2=numStack.pop();
            oper=operStack.pop();
            res=numStack.cal(num1,num2,oper);
            numStack.push(res);
        }
        //将数栈的最后数,pop出,就是结果
        int res2=numStack.pop();
        System.out.printf("表达式%s = %d",expression,res2);
    }
    public static void main(String[] args) {
        String expression="(2-4)+3*6";
        compute(expression);
    }
}

中缀表达式转后缀表达式

大家可能会想:既然中缀表达式可以计算结果,我们为什么还要学习后缀表达呢? 而且后缀表达式看起来不容易理解:
后缀表达式是计算机通常用的方法,因为它不用考虑优先级,可以直接进行出栈运算即可。
所以我们先学习一下如何使中缀表达式转化为后缀表式。
需要了解其法则:

  • 中缀转后缀表达式
  • 1.初始化两个栈,运算栈S1和存储中键结果的栈s2
  • 2.从左至右扫描中缀表达式
  • 3.遇到操作数时,将其压s2
  • 4.遇到运算符时,比较其与s1栈顶运算符的优先级
  • 4.1) 如果s1为空,或栈顶运算符为左括号,则直接将此运算符入栈
  • 4.2)否则,如果优先级比栈顶运算符的高,也将运算符压入s1.
  • 4.3)否则,将s1栈顶的运算符弹出并压入到s2中,再次转到4.1与s1中新的栈顶运算符相比较
  • 5.遇到括号时:
  • 5.1)如果是左括号,则直接入栈
  • 5.2)如果是右括号,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
  • 6)重复步骤2-5 直到表达式的最右边
  • 7)将s1剩余的运算符依次弹出并压入s2
  • 8)依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式

我们来模拟一下其转化过程:

eg:(3+4)*5-2

javascript 前缀匹配 startwith 前缀表达式求值java_栈_04

javascript 前缀匹配 startwith 前缀表达式求值java_栈_05

javascript 前缀匹配 startwith 前缀表达式求值java_算法_06

需要注意的是,这里进行模拟的时候,采用的是两个栈,但是s2只有插入没有输出的过程,到最后运算完后,还需要将s2栈逆序输出,反而增加了复杂度,因此我们s2可以采用队列,list集合等方便输出。
代码:
首先将中缀表达式转为List集合,方便取元素:

//方法:将中缀表达式转成对应的List
    public static List<String> toInfixExpressionList(String s){
        //定义一个List,存放中缀表达式 对应的内容
        List<String> ls=new ArrayList<>();
        int i=0;
        String str;//对多位数进行处理
        char c=' ';
        do {
            //如果c是一个非数字,需要加入ls
            if((c=s.charAt(i))<'0'||(c=s.charAt(i))>'9'){
                ls.add(""+c);
                i++;
            }
            else {
                //如果是数字则需要考虑多位数
                str = "";//先将str置为“”
                while (i<s.length()&&(c=s.charAt(i))>='0'&&(c=s.charAt(i))<='9'){
                    str+=c;//拼接
                    i++;
                }
                ls.add(str);
            }
        }while (i<s.length());
        return ls;
    }

其次将表达式转为后缀表达式:

public  static List<String> reverse(List<String> ls){
      //定义两个栈
        Stack<String> s1=new Stack<>();//符号栈
      //说明:因为s2这个栈,在整个转换过程中,没有pop操作,而且后面还需要逆序输出
        //比较麻烦,则我们此处使用List<String> s2;
        List<String> s2=new ArrayList<>();//储存中间结果的List2
        //遍历ls
        for(String item:ls){
            //如果是数字,则直接放入s2
            if(item.matches("\\d+")){
                s2.add(item);
            }
            //左括号直接入栈
            else if(item.equals("(")){
                s1.push(item);
            }//如果是右括号,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止
            else  if(item.equals(")")){
             while (!s1.peek().equals("(")){
                 s2.add(s1.pop());
             }
             s1.pop();//将(弹出s1栈,消除小括号
            }else {
                //当item的优先级小于或者等于s1栈顶优先级,先将s1栈顶的运算符弹出并加入到s2中,再次转到4.1与s1中新的
                //栈顶运算符相比较
                while (s1.size()!=0&&operation.getValue(s1.peek())>=operation.getValue(item)){
                    s2.add(s1.pop());
                }
                //还需要将item入栈
                s1.push(item);
            }
        }
        while (s1.size()!=0){
            s2.add(s1.pop());
        }
        return s2;//注意因为存放到List中,因此按顺序暑促的就是对应的后缀表达式对应的List
    }

此处s1栈,涉及到优先级的比较:

//编写一个类 可以返回运算符对那个的优先级
class operation{
    private static int add=1;
    private static int sub=1;
    private static int mul=2;
    private static int div=2;
    //写方法 返回其对应的优先级数字
    public static  int getValue(String operation){
        int res=0;
        switch (operation){
            case "+":res=add;break;
            case "-":res=sub;break;
            case "*":res=mul;break;
            case "/":res=div;break;
            default:
                break;
        }
        return res;
    }
}

便可以完成中缀转后缀。

后缀表达式求值(逆波兰表达式求值)

后缀表达式求值比较简单,不需要进行优先级判断,直接出栈计算即可。

  • 从左至右扫描表达式,遇到数字时,将数字压入堆栈
  • 遇到运算符号时,弹出栈顶的两个数,用运算符对他们做相应的计算(次顶元素和栈顶元素),并将结果入栈;
  • 重复上述过程直至表达式最右边,最后运算得出的值即为表达式的结果
    eg:(3+4)*5-2 其对应的后缀表达式:3 4 + 5 * 2 -

代码:

//完成对逆波兰表达式的计算
    public static int compute(List<String> list){
        Stack<String> stack=new Stack<>();
        for(String item:list){
            //使用正则表达式来取数
            if(item.matches("\\d+")){
                stack.push(item);
            }
            else {
                //pop出两个数 并进行运算  再将结果入栈
                int num1=Integer.valueOf(stack.pop());
                int num2=Integer.valueOf(stack.pop());
               int res=0;
                if(item.equals("+")){
                    res=num1+num2;
                }
                else if(item.equals("-")){
                    res=num2-num1;
                }
                else if(item.equals("*")){
                    res=num1*num2;
                }
                else if(item.equals("/")){
                    res=num2/num1;
                }else {
                    throw new RuntimeException("运算符号有误");
                }
                stack.push(String.valueOf(res));
            }
        }
        return Integer.valueOf(stack.pop());
    }
  //将一个逆波兰表达式,依次将数据和运算符放入到Arraylist中
    public static List<String> getListString(String suffixExpression){
       //将sufferExpression分割
       String[] split=suffixExpression.split(" ");
      List<String> list=new ArrayList<>();
      for(String str:split){
          list.add(str);
      }
      return list;
    }

前缀表达式(波兰表达式)

前缀表达式与后缀表达式正好相反,懂了后缀表达式,便可以轻松写出前缀表达式
需要注意:前缀表达式 符号在前,数字在后,我们进行运算时,需要从右至左进行运算,其余步骤一样。
例如,- 1 + 2 3,它等价于1-(2+3)。大家举一反三,便可以自己求出,本文不再赘述。
有什么问题欢迎大家前来提问。