Java数据结构之前缀(波兰表达式)、中缀、后缀表达式(逆波兰表达式)

前缀(波兰表达式)、中缀、后缀表达式(逆波兰表达式)简单介绍:

  • 前缀表达式又称波兰表达式,前缀表达式的运算符位于操作数之前 例如:+ + 7 * 5 8 - / 10 5 6
  • 中缀表达式(人们的最爱)就是我们常见的运算表达式 例如:5*8+7+10/5-6
  • 后缀表达式 (计算机的最爱)又称逆波兰表达式,后缀表达式的运算符位于操作数之后 例如 :7 5 8 * + 4 5 / 6 - +

思考:一个计算表达式是如何得到前中后之分的?

首先看到前中后,我们是不是有一种似曾相识。你想到的是不是二叉树的三种遍历

二叉树的三种遍历:前序遍历 :根结点 ---> 左子树 ---> 右子树 

                                 中序遍历:左子树---> 根结点 ---> 右子树

                                 后续遍历:左子树 ---> 右子树 ---> 根结点

他们分别对应了 前缀表达式中缀表达式后缀表达式。因此给我们一个表达式我们同样可以画出一棵语法树。

例如:5*8+7+10/5-6 画出其语法树:

同理我们可以根据该语法树,得到表达式的前缀表达式,和后缀表达式。

下面我们分别介绍他们的计算机求值:

前缀表达式计算机求值:

拿到一个前缀表达式,从右向左开始遍历,遇到数字将其压入栈(stack先进后出)中,当遇到运算符,取出栈顶元素和次栈顶元素,进行运算符对应的运算操作,然后将结果再次压入栈中。重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果。

中缀表达式计算机求值:

给一个我们最喜欢的中缀表达式,这个时候我们需要两个栈,一个栈存放运算符,一个栈存放数字。

  1. 从左向右开始遍历,
  2. 当遇到数字时,将其压入数字栈。
  3. 当遇到运算符号时,判断运算符栈是否为空,若为空直接压入。反之,与当前栈顶元素比较优先级,如果当前栈内元素优先级高,则取出运算符栈顶元素,取出数字栈栈顶元素和次栈顶元素,进行运算符对应的运算操作,然后将结果再次压入数字栈中。若当前栈顶元素的优先级较低,则直接将该运算符压入运算符栈中。
  4. 详细过程见代码,这里只是一个不够完善的方法,一般采用中缀表达式转前缀表达式来进行计算机求值
package stack;

import java.util.Stack;

public class CalculatorStack {
    public static void main(String agrs[]) throws NumberFormatException {
        Stack<Integer> number = new Stack<Integer>(); //数字栈
        Stack<Character> oper = new Stack<Character>(); //运算符栈
        String str = "5*5/5+7-2*6+1*4";
        char[] ch = str.toCharArray(); //将字符串变为字符数组。
        boolean flage = false;
        int ans = 0;
        for (int i = 0; i < str.length(); i++) {
            if (JudgeOper(ch[i])) {//判断是字符还是数字 //如果是字符为true
                if (ch[i] == '-') {    //当为减符号是,让该符号与下一位数字捆绑,让其变为负数,便于计算。
                    flage = true;      // 如果不这样 5*5/5+7-2*6+14-4 结果将是 -10;
                    ch[i] = '+';       //让其加上一个负数。
                }
                if (oper.empty()) oper.push(ch[i]);//当为空的时候返回true
                    //如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符,就需要从数栈中pop出两个数,
                    //在从符号栈中pop出一个符号,进行运算,将得到结果,入数栈,然后将当前的操作符入符号栈
                else if (priority(ch[i]) <= priority(oper.peek())) {
                    int num1 = number.pop();
                    int num2 = number.pop();
                    char item = oper.pop();
                    ans = Calculator(num1, num2, item);
                    //把运算的结果如数栈
                    number.push(ans);
                    //然后将当前的操作符入符号栈
                    oper.push(ch[i]);
                } else {
                    //如果当前的操作符的优先级大于栈中的操作符, 就直接入符号栈.
                    oper.push(ch[i]);
                }
            } else {//如果是数字
                String numberString = null;
                if (flage) {
                    numberString = "-" + ch[i]; //此处是将数字变为负数
                    flage = false;
                } else numberString = ch[i] + "";//存数字。

                int j = 0;//"5*5/5+7-6*2+14-4"
                for (j = i + 1; j < str.length(); j++) {   // 如果下一位还是数字 则继续。
                    if (!JudgeOper(ch[j])) numberString += ch[j]; //如果为多位数。
                    else break;
                }
                i = j - 1;
                int a = Integer.parseInt(numberString);  //将数字字符串变为数字。
                number.push(a);
            }
        }
        // 当字符串遍历结束,但是栈中不一定为空,此时如果栈中有剩余,
        // 则每从运算符栈取出一个元素,则从数字栈中取出两个元素
        // 进行运算符对应的运算操作,然后将结果再次压入栈中
        //最后数字栈中剩余的的元素即为表达式结果。
        while (true) {
            if (oper.isEmpty()) {
                break;
            }
            int num1 = number.pop();
            int num2 = number.pop();
            char m = oper.pop();
            ans = Calculator(num1, num2, m);
            System.out.println(num1+" "+num2+"="+ans);
            number.push(ans);
        }
        System.out.printf("表达式%s = %d", str, number.pop());
    }

    public static boolean JudgeOper(char val) {
        return val == '+' || val == '-' || val == '*' || val == '/';
    }

    //返回运算符的优先级,优先级是程序员来确定, 优先级使用数字表示
    //数字越大,则优先级就越高.
    public static int priority(int oper) {
        if (oper == '*' || oper == '/') {
            return 100;
        } else if (oper == '+' || oper == '-') {
            return 1;
        } else {
            return -1; // 假定目前的表达式只有 +, - , * , /
        }
    }

    public static int Calculator(int num1, int num2, char w) {
        int ans = 0;
        switch (w) {
            case '+':
                ans = num1 + num2;
                break;
/*            case '-':
                ans = num2 - num1;
                break;*/
            case '*':
                ans = num1 * num2;
                break;
            case '/':
                ans = num2 / num1;
                break;
        }
        return ans;
    }
}

 

后缀表达式计算机求值: 

当拿到一个计算级最爱的表达式,从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 和 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果

package stack;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class 后缀表达式 {

    public static void main(String[] args) {
        
		//先定义给逆波兰表达式
		//测试
		//说明为了方便,逆波兰表达式 的数字和符号使用空格隔开
		String suffixExpression = "5 5 * 5 / 7 6 - 2 * + 14 + 4 -"; // 76
		//思路
		//1. 先将 "5 5 * 5 / 7 6 - 2 * + 14 + 4 -" => 放到ArrayList中
		//2. 将 ArrayList 传递给一个方法,遍历 ArrayList 配合栈 完成计算

		List<String> list = getListString(suffixExpression);
		System.out.println("rpnList=" + list);
		int res = calculate(list);
		System.out.println("计算的结果是=" + res);

    }

    //将一个逆波兰表达式, 依次将数据和运算符 放入到 ArrayList中
    public static List<String> getListString(String suffixExpression) {
        //将 suffixExpression 分割
        String[] split = suffixExpression.split(" ");
        List<String> list = new ArrayList<String>();
        for(String ele: split) {
            list.add(ele);
        }
        return list;
    }

    //完成对逆波兰表达式的运算
    public static int calculate(List<String> ls) {
        // 创建给栈, 只需要一个栈即可
        Stack<String> stack = new Stack<String>();
        // 遍历 ls
        for (String item : ls) {
            // 这里使用正则表达式来取出数
            if (item.matches("\\d+")) { // 匹配的是多位数
                // 入栈
                stack.push(item);
            } else {
                // pop出两个数,并运算, 再入栈
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int res = 0;
                if (item.equals("+")) {
                    res = num1 + num2;
                } else if (item.equals("-")) {
                    res = num1 - num2;
                } else if (item.equals("*")) {
                    res = num1 * num2;
                } else if (item.equals("/")) {
                    res = num1 / num2;
                } else {
                    throw new RuntimeException("运算符有误");
                }
                //把res 入栈
                stack.push("" + res);
            }

        }
        //最后留在stack中的数据是运算结果
        return Integer.parseInt(stack.pop());
    }

}

//编写一个类 Operation 可以返回一个运算符 对应的优先级
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 result = 0;
        switch (operation) {
            case "+":
                result = ADD;
                break;
            case "-":
                result = SUB;
                break;
            case "*":
                result = MUL;
                break;
            case "/":
                result = DIV;
                break;
            default:
                System.out.println("不存在该运算符" + operation);
                break;
        }
        return result;
    }

}

 

中缀表达式转后缀表达式计算机求值:

后缀表达式适合计算式进行运算,但是人们却不太容易写出来,尤其是表达式很长的情况下,因此在开发中,我们需要将 缀表达式转成后缀表达式。

具体过程如下:

  • 始化两个栈:运算符栈operStack和储存中间结果的temp链表中
  • 左至右扫描中缀表达式
  • 到操作数时,将放入temp链表;
  • 到运算符时,比较其与s1栈顶运算符的优先级: 
  •             如果operStack为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈
  •             若优先级比栈顶运算符的高,也将运算符压入operStack;
  •             否则,将operStack栈顶的运算符弹出并放入到temp链表中,
  •             再次判断如果operStack不为空,则与operStack中新的栈顶运算符相比较
  • 遇到括号时:
  •             如果是左括号“(”,则直接压入operStack
                如果是右括号“)”,则依次弹出operStack栈顶的运算符,并放入temp链表,直到遇到左括号为止,此时将这一              对括号丢弃
  • 重复步骤,直到表达式的最右
  • 将operStack中剩余的运算符依次弹出并放入temp链表
  • 依次取出temp链表中的元素并输出,结果即为中缀表达式对应的后缀表达
package stack;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class 中缀表达式转后缀 {
    public static void main(String agrs[]) {
        String str = "5*5/5+(7*6-2)*2+14-4";//中缀表达式。
        List<String> infixExpressionList = toInfixExpressionList(str);
        System.out.println("中缀表达式对应的List=" + infixExpressionList);
        List<String> suffixExpreesionList = parseSuffixExpreesionList(infixExpressionList);
        System.out.println("后缀表达式对应的List" + suffixExpreesionList); //ArrayList [1,2,3,+,4,*,+,5,–]
        System.out.printf("expression=%d", calculate(suffixExpreesionList));
    }

    //中缀--》后缀
    public static List<String> parseSuffixExpreesionList(List<String> ls) {
        Stack<String> operStack = new Stack<String>(); //字符栈。
        //Stack<String> tempStack = new Stack<String>();//储存中间结果
        //tempStack 栈 整个过程中没有pop操作可以用 list代替。
        List<String> temp = new ArrayList<String>();

        //遍历ls
        for (String ch : ls) {
            //如果是一个数,加入temp。
            if (ch.matches("\\d+")) {
                temp.add(ch);
            } else if (ch.equals("(")) {//进入符号栈
                operStack.push(ch);
            } else if (ch.equals(")")) {
                while (!operStack.peek().equals("(")) {
                    temp.add(operStack.pop());
                }
                operStack.pop();
            } else {
                //当ch的优先级小于等于
                while (operStack.size() != 0 && Operation.getValue(operStack.peek()) >= Operation.getValue(ch)) {
                    temp.add(operStack.pop());
                }
                operStack.push(ch);
            }

        }
        while (operStack.size() != 0) {
            temp.add(operStack.pop());
        }
        return temp;
    }

    //字符串转ArrayList
    public static List<String> toInfixExpressionList(String s) {
        //定义一个List  存放中缀表达式的对应位置
        List<String> ls = new ArrayList<String>();
        int i = 0;
        String str;//多位拼接
        char c;
        do {
            //如果C是一个非数字 加入Ls
            if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57) {
                ls.add("" + c);
                i++;
            } else {  //如果是一个数,考虑多位数。
                str = "";
                while ((i < s.length()) && (c = s.charAt(i)) >= 48 && (c = s.charAt(i)) <= 57) {
                    str += c;
                    i++;
                }
                ls.add(str);
            }
        } while (i < s.length());
        return ls;
    }

//偏写优先级;

    public static int calculate(List<String> ls) {
        // 创建给栈, 只需要一个栈即可
        Stack<String> stack = new Stack<String>();
        // 遍历 ls
        for (String item : ls) {
            // 这里使用正则表达式来取出数
            if (item.matches("\\d+")) { // 匹配的是多位数
                // 入栈
                stack.push(item);
            } else {
                // pop出两个数,并运算, 再入栈
                int num2 = Integer.parseInt(stack.pop());
                int num1 = Integer.parseInt(stack.pop());
                int res = 0;
                if (item.equals("+")) {
                    res = num1 + num2;
                } else if (item.equals("-")) {
                    res = num1 - num2;
                } else if (item.equals("*")) {
                    res = num1 * num2;
                } else if (item.equals("/")) {
                    res = num1 / num2;
                } else {
                    throw new RuntimeException("运算符有误");
                }
                //把res 入栈
                stack.push("" + res);
            }

        }
        //最后留在stack中的数据是运算结果
        return Integer.parseInt(stack.pop());
    }
}

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 result = 0;
        switch (operation) {
            case "+":
                result = ADD;
                break;
            case "-":
                result = SUB;
                ;
                break;
            case "*":
                result = MUL;
                break;
            case "/":
                result = DIV;
                break;
            default:
                System.out.println("我是一个"+operation);
                break;
        }
        return result;
    }
}

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

@李思成

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

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

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

打赏作者

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

抵扣说明:

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

余额充值