[Java]算術表達式求值之一(中序表達式轉后序表達式方案)


第二版請見:https://www.cnblogs.com/xiandedanteng/p/11451359.html

入口類,這個類的主要用途是粗篩用戶輸入的算術表達式:

package com.hy;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

// 此類用於把算術表達式送入解析器
public class Inlet {
    public static void main(String[] args) throws IOException{
        // 取得用戶輸入的表達式
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 
        String rawExpression = null; 
        System.out.print("請輸入算術表達式:"); 
        rawExpression = br.readLine(); 
        
        // 得到合法的算術表達式
        String expression="";    
        for(int i=0;i<rawExpression.length();i++){
            // 拿到表達式的每個字符
            char c=rawExpression.charAt(i);
            //System.out.print(c+","); 
            
            if(Character.isDigit(c) || c=='+' || c=='-' || c=='*' || c=='/' || c=='(' || c==')' ){
                //System.out.print(c);
                expression+=c;
            }else{
                System.out.print(" "+c+"不是合法的算術表達式字符.");
                System.exit(0);
            }
        }
        
        // 送去解析
        Parser p=new Parser(expression);
        //p.print();
        
        // 轉為后序表達式
        Trans t=new Trans(p.getList());
        //t.print();
        
        // 計算結果
        Calculator c=new Calculator(t.getPostfixList());
        System.out.print(expression+"="+c.getResult());
    }
}

算術表達式解析器類,它主要起一個詞法分析器的作用,由於算術表達式詞法較簡單,因此逐字讀入處理也能完成任務,他的輸入是如23+4*(5+6)這種算術表達式,處理完成以后得到23,+,4,*,(,5,+,6,)這些包含操作數和操作符的列表:

package com.hy;

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

// 此類用於將算術表達式解析成包含操作數和操作符的鏈表
public class Parser {
    private List<String> list;// 用於存儲表達式的鏈表
    
    public List<String> getList() {
        return list;
    }

    public Parser(String expression){
        list=new ArrayList<String>();
        
        String str="";
        for(int i=0;i<expression.length();i++){
            char c=expression.charAt(i);
            
            if(Character.isDigit(c)){
                str+=c;
            }else{
                if(str.length()>0){// 此判斷是因為有+(這種符號相連的情況
                    //System.out.println(str);
                    list.add(str);
                    str="";
                }
                
                //System.out.println(c);
                list.add(String.valueOf(c));
            }
        }
        
        if(str.length()>0){// 此判斷是因為可能表達式不是以=結尾
            //System.out.println(str);
            list.add(str);
            str="";
        }
    }
    
    public void print(){
        for(String str:list){
            System.out.println(str);
        }
    }
}

將中序表達式轉后序表達式的轉換類,他接收來自Parser的包含操作符和操作數的列表,然后根據規則將算術表達式轉化成后序表達式,利用的數據結構是棧java.util.Statck,轉化的規則如下:

見到操作數->直接送到postfixList中
見到操作符->將棧頂輸出,直到棧頂優先級小於該操作符,最后把該操作符壓入棧
見到左括號 ->入棧
見到右括號 ->將棧中在左括號之后的操作符全部輸出
(以上'棧'在代碼中指的是Trans類的成員變量Stack)

package com.hy;

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

// 此類用於將中序表達式轉譯成后序表達式
public class Trans {
    private Stack<String> stack;// 用於存儲操作符的棧
    private List<String> postfixList;// 用於存儲后序表達式的鏈表
    
    public List<String> getPostfixList() {
        return postfixList;
    }

    public Trans(List<String> list){
        stack=new Stack<String>();
        postfixList=new ArrayList<String>();
        
        for(String str:list){
            // 這個分支是當前項是操作符號的情況
            if(str.equals("+") || str.equals("-") || str.equals("*") || str.equals("/") || str.equals("(") || str.equals(")")  ){
                String opThis=str;
                
                if(stack.size()==0){
                    // 如果棧為空,直接把操作符推入棧
                    stack.push(opThis);
                }else if(str.equals("(")){
                    // 如果操作符是左括號,直接推入棧
                    stack.push(opThis);
                }else if(str.equals(")")){
                    // 如果操作符是右括號,則往前找左括號,將左括號之后的操作符放到后續表達式列表中
                    
                    while(stack.peek().equals("(")==false){ // stack.peek()是取棧頂元素而不彈出
                        postfixList.add(stack.pop());
                    }
                    
                    stack.pop();// 左括號丟棄,由此完成了去括號的過程
                }else{
                    // 看棧頂元素,如果它優先級大於等於當前操作符的優先級,則彈出放到后續表達式列表中
                    while( stack.size()>0 && (getOpLevel(stack.peek())>=getOpLevel(opThis)) ){
                        postfixList.add(stack.pop());                        
                    }
                    
                    stack.push(opThis);// 當前操作符入棧
                }
        
            }else{
                // 這個分支是當前項是操作數的情況
                postfixList.add(str);// 操作數直接入棧
            }            
        }
        
        // 將棧中余下的操作符彈出放到后續表達式列表中
        while(stack.size()>0){
            String opTop=stack.pop();
            postfixList.add(opTop);
        }
    }
    
    // 取得操作符的等級
    private int getOpLevel(String op){
        if(op.equals("+") || op.equals("-") ){
            return 0;
        }else if(op.equals("*") || op.equals("/") ){
            return 1;
        }
        
        return -1;
    }
    
    public void print(){
        for(String str:postfixList){
            System.out.print(str);
        }
    }
}

計算后續表達式運算結果類,它接受經過Trans類處理的postfixList,又采用了棧進行輔助,計算結果方式是見到操作數先入棧,見到操作符則從棧中彈出兩個操作數進行運算,得到結果后再入棧,執行完畢后彈出棧的頂項(必是最后一項)即是算術表達式的最終結果:

package com.hy;

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

// 此類用於計算后續表達式的值
public class Calculator {
    private Stack<String> stack;
    
    public Calculator(List<String> list){
        stack=new Stack<String>();
        
        for(String str:list){
            // 這個分支是當前項是操作符號的情況
            if(str.equals("+") || str.equals("-") || str.equals("*") || str.equals("/") || str.equals("(") || str.equals(")")  ){
                float op2=Float.parseFloat(stack.pop());
                float op1=Float.parseFloat(stack.pop());
                float result=0;
                
                if(str.equals("+")){
                    result=op1+op2;
                }else if(str.equals("-")){
                    result=op1-op2;
                }else if(str.equals("*")){
                    result=op1*op2;
                }else if(str.equals("/")){
                    result=op1/op2;
                }
        
                stack.push(String.valueOf(result));
            }else{
                // 如果是操作數先直接入棧
                stack.push(str);
            }            
        }
    }
    
    // 取得結果
    public String getResult(){
        return stack.peek();
    }
}

運行結果:

請輸入算術表達式:(2+3)*6-20
(2+3)*6-20=10.0

請輸入算術表達式:13-5*(1+2)
13-5*(1+2)=-2.0

請輸入算術表達式:1+2+3+4+5+6+7+8+9+10
1+2+3+4+5+6+7+8+9+10=55.0

 

到這里,基本上算是實現了Javascript里的eval函數,當然還有需要完善的地方,比如用正則表達式對輸入的算術表達式進行預驗證,用二叉樹形成語法結構等,這些留待日后完成。可以想象如果沒有利用后續表達式助力,代碼不知會寫得多么復雜難懂。由此可知除了分解問題外,合適的數學工具也是改善代碼的重要手段。

 

喝水不忘挖井人,我的參考資料如下:

1.Java數據結構與算法(第二版) [美]Robert Lafore著

2.棧的應用--中序表達式轉后序表達式  https://www.cnblogs.com/bgmind/p/3989808.html

--END--2019年9月2日13點35分


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM