逆波兰算法

逆波兰算法

简单问题引入

一个只涉及个位的四则运算表达式,例如:
7 * 2 * 2 - 5 * 1 - 5 + 3 * 4(忽略空格)
现在要求设计一个简单算法,完成计算(肯定不是让你直接输入表达式计算)

先说一下大致思路:
利用两个栈,一个放数字,记为nums—数字栈;一个放计算符号,记为symbol—符号栈

  1. 扫描表达式,如果是数字则直接入数字栈nums
  2. 如果是运算符,分以下情况:
    1) 如果符号栈为空或者,当前符号优先级>当前栈顶符号优先级:直接push
    2)如果当前符号优先级<=当前栈顶符号优先级: 取出nums栈顶两位元素和符号栈栈顶元素进行相应计算
  3. 最后的情况可能是符号栈内还剩余一个符号,再进行运算即可
  4. 这时nums内的栈顶元素即为答案

下面看代码:

public class SimpleException {
    public static void main(String[] args) {
        String exception = "7*2*2-5*1-5+3*4";
        System.out.println(exception + " = " + solution(exception));
    }

    static int solution(String exception){
        Stack nums = new Stack();//数字栈
        Stack symbol = new Stack();//符号栈

        char[] chars = exception.toCharArray();
        for (char chr: chars){//扫描表达式每个字符
            if (!isSymbol(chr)){//判断是不是运算符
                int target = Character.getNumericValue(chr);
                nums.push(target);//如果是数字,直接入数字栈
            }else {//如果是运算符
                if (symbol.isEmpty() || getPriority((char)symbol.peek())<getPriority(chr)){//如果运算符栈顶为空
                    symbol.push(chr);//直接入栈
                }else if (getPriority((char)symbol.peek())>=getPriority(chr)){
                    /*如果当前符号栈栈顶的优先级大于等于当前扫描符号
                     *先弹出数字栈两个数字,再弹出符号栈栈顶元素,进行计算,结果再入数字栈,别忘记将当前符号入栈
                     */
                    int result = calculate(nums,symbol);
                    nums.push(result);
                    //这一步是为了防止类似-5-5这样的情况,计算的时候会变成5-5.保证符号栈最多只有一个符号
                    if (!symbol.isEmpty()){
                        result = calculate(nums,symbol);
                        nums.push(result);
                    }
                    symbol.push(chr);
                }
            }
        }
        while(!symbol.isEmpty()){
            int result = calculate(nums,symbol);
            nums.push(result);
        }
        return (int)nums.pop();
    }

    private static int calculate(Stack nums, Stack symbol) {
        int num1 = (int)nums.pop();
        int num2 = (int)nums.pop();
        char sym = (char)symbol.pop();
        int result = 0;
        switch (sym){
            case '+':
                result = num1 + num2;
                break;
            case '-':
                result = num2 - num1;
                break;
            case '*':
                result = num1 * num2;
                break;
            case '/':
                result = num2 / num1;
        }
        return result;
    }

    static boolean isSymbol(char chr){
        if (chr == '+' || chr == '-' || chr == '*' ||chr == '/')
            return true;
        return false;
    }
    static int getPriority(char symbol){
        if(symbol == '+' || symbol == '-')
            return 1;
        else return 2;
    }
}

这段代码解决不了多位数计算,这里讲一下思路:

  1. 处理不了多位数主要因为我们是一旦发现不是运算符就直接入栈,所以出错。
  2. 当发现不是运算符时先不要入栈,先保存(可以用字符串保存,后面发现还是数字就拼接,然后取值用Integer的paseInt方法)
  3. 继续向后扫描,当发现是运算符时就取出数字然后压入nums

再想想如果假如括号呢?情况就会更复杂一些。
下面介绍一种算法可以比较简单地处理:逆波兰算法
在此之前需要知晓前置知识:

什么是前、中、后缀表达式?

中缀表达式

我们正常使用的表达式,如上面的7 * 2 * 2 - 5 * 1 - 5 + 3 * 4

前缀表达式

又称为波兰表达式,运算符都位于操作数之前,例如(3 + 4) * 5 - 6 的前缀表达式就是 - * + 3 4 5 6
计算机在进行计算式,从右向左扫描,遇到数字就push进数字栈,遇到运算符就进入运算流程

后缀表达式

又称为逆波兰表达式,还是以(3 + 4) * 5 - 6 为例,它的后缀表达式是3 4 + 5 * 6 -
这里多举几个例子:

中缀表达式后缀表达式
a + ba b +
a + ( b - c)a b c - +
a + ( b - c) * da b c - d * +
a + d * ( b - c)a d b c - * +
a = 1 + 3a 1 3 + =

转换的时候思考计算机要怎么计算(前面的例子)就会好理解很多
计算方式和前置表达式类似,只是从左往右扫描。
看一下具体的计算过程:

public class InversePolish {
    public static void main(String[] args) {
        //这里偷个懒,用空格分割
        String exception = "3 4 + 5 * 6 -";
        System.out.println(solution(exception));
    }
    static int solution(String exception){
        Stack<String > stack = new Stack();
        String[] strs = exception.split(" ");
        for (String str:strs) {
            if (str.matches("\\d+")){//匹配多位数
                stack.push(str);
            }else {
                int result = calculate(stack,str);
                stack.push(result+"");
            }
        }
        return Integer.parseInt(stack.pop());
    }
    static int calculate(Stack stack,String str) {
        int num1 = Integer.parseInt((String) stack.pop());
        int num2 = Integer.parseInt((String) stack.pop());
        int result = 0;
        switch (str){
            case "+":
                result = num1 + num2;
                break;
            case "-":
                result = num2 - num1;
                break;
            case "*":
                result = num1 * num2;
                break;
            case "/":
                result = num2 / num1;
        }
        return result;
    }
}

从代码来看,逆波兰的计算过程比中缀表达式要简单,麻烦的过程可能就是怎么转化一个中缀表达式为后缀表达式。

逆波兰表达式转化思路

  1. 初始化两个栈:中间结果栈s1和运算符号符号栈s2
  2. 从左往右扫描中缀表达式
  3. 遇到数值,压入s1
  4. 遇到运算符
    1) 如果s2为空,或者栈顶元素是“( ”,则直接入栈
    2)如果优先级比栈顶符号高,也入栈s2
    3)否则,将s2的栈顶元素弹出并压入s2中,再次重复步骤4
  5. 如果遇到括号
    1)如果是“( ”,直接入栈s2
    2)如果是“ )”,依次弹出s2中元素并压入s1,直到遇到“( ”为止,然后抛弃这对括号
  6. 重复步骤2-5,知道扫描完毕
  7. 最后只需要将s1中的结果依次弹出,并逆序保存即为后缀表达式

下面代码实现:

public class InversePolish {
    public static void main(String[] args) {
        //这里偷个懒,用空格分割
        String exception = "( 3 + 4 ) * 5 - 6";
        String postE = postFix(exception);//转化为后缀表达式
        System.out.println(solution(postE));//计算后缀表达式
    }
    static Stack<String> s1 = new Stack<>();//存放中间结果
    static Stack<String> s2 = new Stack<>();//存放运算符
    static String postFix(String exception){
        String[] strs = exception.split(" ");
        String str = "";
        for (int i=0;i<strs.length;++i) {
            str = strs[i];
            if (str.matches("\\d+")){//匹配多位数
                s1.push(str);
            }else {
                if (str.equals("(") || s2.isEmpty()) {
                    s2.push(str);
                } else if (str.equals(")")){//“)”的判断应该靠前
                    while (!(s2.peek()).equals("(")){
                        s1.push(s2.pop());
                    }
                    s2.pop();
                }else if ((s2.peek()).equals("(") ||
                        getPriority(str)>=getPriority(s2.peek())){
                    s2.push(str);
                }else if (getPriority(str)<getPriority(s2.peek())){
                    s1.push(s2.pop());
                    return postFix(exception.substring(i+i));//和新的栈顶元素对比,重走流程
                }
            }
        }
        while(!s2.isEmpty()){//如果最后s2还有剩余符号未添加,则依序添加到s1
            s1.push(s2.pop());
        }
        String inverseResult = "";
        while (!s1.isEmpty()){
            inverseResult = inverseResult + s1.pop() + " ";
        }
        String result = "";
        for (int i = inverseResult.length()-1; i >= 0 ; i--) {
            result = result + inverseResult.substring(i,i+1);
        }
        return result.substring(1);//最后结果前面或有一个空格
    }
    static int getPriority(String symbol){
        if(symbol.equals("-") || symbol.equals("+"))
            return 1;
        else if (symbol.equals("*") || symbol.equals("/"))
            return 2;
        else return 0;
    }
    static int solution(String exception){
        Stack<String > stack = new Stack();
        String[] strs = exception.split(" ");
        for (String str:strs) {
            if (str.matches("\\d+")){//匹配多位数
                stack.push(str);
            }else {
                int result = calculate(stack,str);
                stack.push(result+"");
            }
        }
        return Integer.parseInt(stack.pop());
    }
    static int calculate(Stack stack,String str) {
        int num1 = Integer.parseInt((String) stack.pop());
        int num2 = Integer.parseInt((String) stack.pop());
        int result = 0;
        switch (str){
            case "+":
                result = num1 + num2;
                break;
            case "-":
                result = num2 - num1;
                break;
            case "*":
                result = num1 * num2;
                break;
            case "/":
                result = num2 / num1;
        }
        return result;
    }
}

总结

面试被问到逆波兰算法,只能讲出利用堆栈。复习一下具体流程,欢迎纠错交流。

  • 8
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值