粗浅看 逆波兰式算法

Posted 吴士龙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了粗浅看 逆波兰式算法相关的知识,希望对你有一定的参考价值。

简介

逆波兰表达式是一种十分有用的表达式,它将复杂表达式转换为可以依靠简单的操作得到计算结果的表达式。它的优势在于只用两种简单操作,入栈和出栈就可以搞定任何普通表达式的运算。

实现逆波兰式的算法,难度并不大,但为什么要将看似简单的中序表达式转换为复杂的逆波兰式?原因就在于这个简单是相对人类的思维结构来说的,对计算机而言中序表达式是非常复杂的结构。相对的,逆波兰式在计算机看来却是比较简单易懂的结构。因为计算机普遍采用的内存结构是栈式结构,它执行先进后出的顺序。

在程序设计中,可能碰到需要对字符串数学表达式求值的问题,常用的方法是解析表达式,生成二叉树,然后进行计算。编译器就是使用这种方法来解析程序中的表达式的。这种方法实现起来有点难度,需要考虑运算符的优先级,括号的配对,堆栈的使用等等。我们正常情况下看到的数学表达式如果用二叉树遍历的话,恰好是中序遍历,故叫做中序表达式。除此之外,还有前序表达式,后序表达式。如:a+b+c(中序),++abc(前序),ab+c+(后序),如果表达式含有×,/,()等就更复杂了。

后缀表达式也称逆波兰表达式 因其使表达式求值变得轻松,所以被普遍使用。   

优先级

优先级分为栈内优先级isp(In stack priority)和栈外优先级icp(In coming priority)。除了括号以外,其他运算符进栈后优先级都升1,这样可以体现在中缀表达式中相同优先级的操作符自左向右计算的要求,让位于栈顶的操作符先退栈并输出。各运算符及符号优先级:


逆波兰式

逆波兰式(Reverse Polish Notation, RPN)也成为后缀表达式,更加广为人知一些,和前缀表达式刚好相反,是将操作符号放置于操作数之后,比如2 + 3 * (5 - 1)用逆波兰式来表示则是:2 3 5 1 - * +。

逆波兰式的计算也是从左往右依次读取,当读到操作符时,将之前的两个操作数做计算,然后替换这两个操作数和操作符,接着读取,重复此步骤。对于这个表达式,读到5 1 -,得到4,然后读取乘号,取出前面的3和上一步的计算结果4,并计算,到12,接着读取加号+,计算2 12 +得到14,计算结束。

上面这个步骤可以很容易的用栈来实现:

从左往右依次读取表达式,如果是数字则将该数字压栈,如果是符号,则将之前的两个数字出栈,做计算后,将计算结果压栈,直到表达式读取结束。栈中剩下的一个数就是计算结果。

逆波兰式看起来像波兰式反过来,比如5 + 1的波兰式是+ 5 1,逆波兰式为5 1 +或者1 5 +。也很明显,逆波兰式并不是简单的将波兰式反过来,因为,减法和除法中减数和被减数、除数与被除数是不能交换的,即- 10 5和- 5 10就完全不一样。

Demo

基于JAVA语言

public class InversePoland 
    // 9+(3-1)*3+10/2 = 20
    //private static String[] ss = new String[]"9","+","(","3","-","1",")","*","3","+","10","/","2";
     
    //24+3-8*(6-3)*2+10-3
    private static String[] ss = new String[]"24","+","3","-","8","*","(","6","-","3",")","*","2","+","10","-","3";
    private static Stack<String> stack = new Stack<String>();
     
    //判断是否为数字
    private boolean isNum(String s) 
        if (s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/") || s.equals("(") || s.equals(")")) 
            return false;
        
        return true;
    
     
    //获取符号优先级
    private int getPriority(String s) 
        if (s.equals("+") || s.equals("-")) 
            return 1;
         else if (s.equals("*") || s.equals("/")) 
            return 2;
        
        return -1;
    
     
    //输出字符串
    private void print(String s) 
        System.out.print(s + " ");
    
     
    //符号操作
    private void symbolOperation(String s) 
        if (stack.size() == 0 || s.equals("("))  //栈为空,直接进栈
            stack.push(s);
         else if (s.equals(")"))   //当前字符为“)”,则将栈中(以上的全部出栈输出
            while (stack.size() > 0) 
                String pop_s = stack.pop();
                if(pop_s.equals("(")) 
                    break;
                 else 
                    print(pop_s);
                
             
         else 
            int pri_s = getPriority(s);
            while (stack.size() > 0 ) 
                String top_s = stack.lastElement();
                if (top_s.equals("(")) 
                    stack.push(s);
                    return;
                 else 
                    int top_pri = getPriority(top_s);
                    if (pri_s <= top_pri) 
                        String pop_s = stack.pop();
                        print(pop_s);
                     else 
                        break;
                    
                
            
            stack.push(s);
        
    
     
    public void test() 
        for (String s : ss) 
            if (isNum(s)) 
                print(s);
             else 
                symbolOperation(s);
            
        
        while (stack.size() > 0) 
            String pop_s = stack.pop();
            print(pop_s);
        
    

输出结果

24 3 + 86 3 - * 2 * - 10 + 3 – 

业务思想

关于逆波兰式的学习,是对于堆和栈的深入理解,对于学习数据结构和算法是必要的。

感受一下逆波兰式的思考方式,你收获甚多!



以上是关于粗浅看 逆波兰式算法的主要内容,如果未能解决你的问题,请参考以下文章

波兰式与逆波兰式的转换和表达式求值

(C语言中)逆波兰算法(及计算器)

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

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

JavaScript中缀表达式转为逆波兰式(四则运算)

逆波兰计算器