1. 程式人生 > >一個算數表示式的字首中綴和字尾(可進行負數運算)

一個算數表示式的字首中綴和字尾(可進行負數運算)

一個算數表示式的字首中綴和字尾

目錄

1. 關於字首、中綴和字尾

  1. 中綴表示式:就是我們常見的算數表示式,有優先順序和括號,例如:3+4*(4+5 )。這個對於我們來說很好理解,但是對於計算機來說就比較麻煩。
  2. 字首表示式:字首表示式是一種沒有括號的算術表示式,與中綴表示式不同的是,其將運算子寫在前面,運算元寫在後面。為紀念其發明者波蘭數學家Jan Lukasiewicz,字首表示式也稱為“波蘭式”。例如,- 1 + 2 3,它等價於1-(2+3)。
  3. 字尾表示式:不包含括號,運算子放在兩個運算物件的後面,所有的計算按運算子出現的順序,嚴格從左向右進行(不再考慮運算子的優先規則,如:(2 + 1) * 3 , 即2 1 + 3 *

2. 中綴轉字首、字尾的演算法思想及程式碼實現

2.1 中綴轉字首

中綴轉字首演算法思想:
1. 初始化兩個棧s1,s2
2. 從右向左掃描中綴表示式,若是數字,將數字壓入棧s1
3. 若遇到運算子,則將其與棧s2棧頂的運算子優先順序作比較
3.1. 若棧s2為空,則將其壓入棧
3.2. 如果棧s2不為空,且不是右括號,且和棧s2的棧頂的運算子優先順序相同或比其高,則將這個運算子壓入棧s2
3.3. 如果遇到的運算子比棧頂運算子優先順序低,則將從s1 中彈出兩個數字與s1棧頂的運算子做運算,將結果壓入棧s1,然後回到3.1
4. 如果遇到右括號,將右括號壓入棧s2
5. 如果遇到左括號,則s2棧頂元素與右括號做比較,如果不是則從s1 中彈出兩個數字與s1棧頂的運算子做運算,將結果壓入棧s1。
6. 如果s2不為空,則從s1 中彈出兩個數字與s1棧頂的運算子做運算,將結果壓入棧s1,直到s2為空
7. 如果最終結果中只有棧s1只有一個元素,則這個結果即為正確結果。
中綴轉字首的程式碼實現如下圖所示:

import java.math.BigDecimal;
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Scanner;
import java.util.Stack;

public class CenterToFront {


    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        while (true) {
            System.out.print("請輸入:"
); String s = sc.nextLine(); String result = toFront(s); if (result != "出錯") { System.out.println("結果是:" + result); } else { System.out.println(s); } } } static String operator = "+-*/"; // 用於判斷是否是操作符 // 用於判斷優先順序 static HashMap<Character, Integer> priority = new HashMap<>(); static { priority.put('+', 1); priority.put('-', 1); priority.put('*', 2); priority.put('/', 2); priority.put(')', 0); } public static String toFront (String str) { ArrayDeque<Character> s1 = new ArrayDeque<>(); // 用於儲存運算子 ArrayDeque<Double> s2 = new ArrayDeque<>(); // 用於儲存數字 // 這裡的這個棧不使用ArrayDeque是 // 因為當ArrayDeque存入Object元素時, // 會將同一種類型的元素放在一塊,這是一個坑點 Stack<Object> s3 = new Stack<>(); char ch, temp; // ch儲存每次遍歷時,temp後面用於暫時儲存字元,後面會用到 // 用於儲存數字的索引,從右向左遍歷是frontDouble儲存的是數字 int frontDouble = -1; int len = str.length(); // 用於儲存字串的字串的長度 for (int i = len - 1; i >= 0; i--) { ch = str.charAt(i); // charAt()方法可以返回字串索引處的字元 if (Character.isDigit(ch)) { // Character的isDigit()可以用來判斷數字是否是字元 frontDouble = readFrontDouble(str, i); // 該方法用於算該數字從索引處到往前遍歷找到第一個非數字的索引 if (frontDouble == -1) { return "出錯"; } // String類的substring(int a, int b)用於返回該字串從a到b-1處的一個子字串 // Double.parseDouble(String str) 將一個自負串解析成一個double型數字 double d = Double.parseDouble(str.substring(frontDouble, i + 1)); if ((int)d == d) s3.add(String.valueOf((int)d)); else s3.add(String.valueOf(d)); s2.push(d); i = frontDouble; } /** * 檢視ch是否是運算子 * 若是運算子則與s1棧頂的運算子作比較 * 1. 若棧頂元素為空,則將ch壓入棧s1 * 2. 若棧頂元素不為空,且ch的優先順序比s1棧頂優先順序高 * 則將ch壓入棧s1 * 3. 若棧頂元素不為空,且ch的優先順序比s1棧頂優先順序低 * 則從棧s2中彈出兩個數、從棧s1中彈出一個運算子, * 然後做運算,將元素所得的結果壓入棧s2 */ else if (operator.indexOf(ch) != -1) { while (!s1.isEmpty() && s1.peek() != ')' && priority.get(ch) < priority.get(s1.peek())) { double d1 = s2.pop(); double d2 = s2.pop(); s3.push(s1.peek().toString()); s2.push(cal(d1, d2, s1.pop())); } s1.push(ch); } // 如遇到右括號,直接壓入棧 else if (ch == ')') s1.push(ch); /** * 如果遇到左括號,並且s1棧頂元素不是右括號 * 則從s2棧頂彈出兩個數字,從s1棧頂彈出一個運算子 * 做運算,將結果存入棧s2 * 迴圈上述過程知道s1棧頂元素是左括號,然後將左括號彈出 * */ else if (ch == '(') { while (s1.peek() != ')') { double d1 = s2.pop(); double d2 = s2.pop(); s3.push(s1.peek().toString()); s2.push(cal(d1, d2, s1.pop())); // 如果沒遇到左括號,但s1棧已經是空的了,那麼肯定出錯了 if (s1.isEmpty()) { return "出錯"; } } s1.pop(); } // 忽略掉空格 else if (ch == ' ') { continue; } // 有其他字元肯定出錯 else { return "出錯"; } } /** * 字串遍歷完成後若s1棧不為空(一般來說坑定不為空) * 則從棧s1中彈出一個符號,從s2棧頂彈出兩個數字做運算 * 後壓入棧s2,直到棧s1為空 */ while (!s1.isEmpty()) { double d1 = s2.pop(); double d2 = s2.pop(); s3.push(s1.peek().toString()); double d3 = cal(d1, d2, s1.pop()); s2.push(d3); } System.out.print("字首是:"); while(!s3.isEmpty()) { System.out.print(s3.pop() + " "); } System.out.println(); // 若最後棧s2中還有超過一個元素,則證明出錯了 double result = s2.pop(); if (!s2.isEmpty()) return "出錯"; if ((int) result == result) return String.valueOf((int)result); else{ return String.valueOf(result); } } /** * 此方法用於計算的d1 op d2 的結果 */ private static double cal(double d1, double d2, char op) throws ArithmeticException{ switch (op) { case '+': return d1 + d2; case '-': return d1 - d2; case '*': return d1 * d2; case '/': if (d1 == 0) { return 1; } return d1 / d2; } return 1; } /** * 這是一個讀取數字位置的方法 * 該方法可以從右向左的讀取一個數字,然後返回該數字在字串中開始的下標 */ private static int readFrontDouble(String str, int start) { int flag = -1; // 用於記錄小數點 char ch; // 用於記錄每次遍歷的字元 for (int i = start; i >= 0; i--) { ch = str.charAt(i); if (ch == '.') { //如果第一次出現小數點,則記錄小數點位置,如果不是那麼肯定出錯 if (flag != -1) { return -1; } else { flag = i; } // 如果該字元是減號,若該字元是第一位(i == 0),則該減號是負號,或者如果該字元的前一個字元不是數字,證明也是負號 } else if (ch == '-' && ((i > 0 && !Character.isDigit((str.charAt(i-1)))) || i == 0)) { return i; // 如果是非數字的肯定該數字已經找到了 }else if (!Character.isDigit(ch)) return i + 1; else if (i == 0) { return 0; } } return -1; } }

2.2 中綴轉字尾

中綴轉字尾的計算方法與中綴轉字首的方法特別相似,只是中綴轉字尾是從前到後的。
1. 初始化兩個棧s1,s2
2. 從左向右掃描中綴表示式,若是數字,將數字壓入棧s1
3. 若遇到運算子,則將其與棧s2棧頂的運算子優先順序作比較
3.1. 若棧s2為空,則將其壓入棧
3.2. 如果棧s2不為空,且不是左括號,且和棧s2的棧頂的運算子優先順序相同或比其高,則將這個運算子壓入棧s2
3.3. 如果遇到的運算子比棧頂運算子優先順序低,則將從s1 中彈出兩個數字與s1棧頂的運算子做運算,將結果壓入棧s1,然後回到3.1
4. 如果遇到左括號,將左括號壓入棧s2
5. 如果遇到右括號,則s2棧頂元素與左括號做比較,如果不是則從s1 中彈出兩個數字與s1棧頂的運算子做運算,將結果壓入棧s1。
6. 如果s2不為空,則從s1 中彈出兩個數字與s1棧頂的運算子做運算,將結果壓入棧s1,直到s2為空
7. 如果最終結果中只有棧s1只有一個元素,則這個結果即為正確結果。

// 字首轉中綴的註釋已經相當的清晰了,所以這裡就不在贅述了
import java.util.ArrayDeque;
import java.util.HashMap;
import java.util.Scanner;
import java.util.Stack;


public class CenterToLast {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in); 
        while(true) {
            System.out.println("請輸入:");
            String str = sc.nextLine();
            System.out.println(toSuffix(str));
        }
    }

    static String operator = "+-*/";    // 用於判斷是否是操作符
    // 用於判斷優先順序
    static HashMap<Character, Integer> priority = new HashMap<>();
    static {
        priority.put('+', 1);
        priority.put('-', 1);
        priority.put('*', 2);
        priority.put('/', 2);
    }


    private static String toSuffix(String str)  {  
        int len = str.length();  
        char c, tempChar;  
        ArrayDeque<Character> s1 = new ArrayDeque<Character>();  
        ArrayDeque<Double> s2 = new ArrayDeque<Double>();  
        Stack<Object> s3 = new Stack<>();
        double number;  
        int lastIndex = -1;  
        for (int i = 0; i < len; ++i) {
            c = str.charAt(i);  
            if (Character.isDigit(c)) {  
                lastIndex = readDouble(str, i);  
                number = Double.parseDouble(str.substring(i, lastIndex));  
                s2.push(number);  
                i = lastIndex - 1;  
                if ((int) number == number)  
                    s3.push((int)number);
                else  
                    s3.push(number);  
            }else if (c == '-' && i == 0) {
                lastIndex = readDouble(str, i + 1);  
                number = Double.parseDouble(str.substring(i, lastIndex));  
                s2.push(number);  
                i = lastIndex - 1;  
                if ((int) number == number)  
                    s3.push((int) number);  
                else  
                    s3.push(number);
            }else if (i > 0 && c == '-' &&
                    (str.charAt(i-1) == '(' 
                || operator.indexOf(str.charAt(i-1)) != -1)) {
                lastIndex = readDouble(str, i + 1);  
                number = Double.parseDouble(str.substring(i, lastIndex));  
                s2.push(number);  
                i = lastIndex - 1;  
                if ((int) number == number)  
                    s3.push((int)number); 
                else  
                    s3.push(number);
            }
            else if (operator.indexOf(c) != -1) {  
                while (!s1.isEmpty() && s1.peek() != '('  
                        && priority.get(c) < priority.get(s1.peek())) {  
                    System.out.print(s1.peek() + " ");  
                    double num1 = s2.pop();  
                    double num2 = s2.pop();
                    s3.push(s1.peek());
                    s2.push(calc(num2, num1, s1.pop()));  
                }  
                s1.push(c);  
            } else if (c == '(') {  
                s1.push(c);  
            } else if (c == ')') {  
                while ((tempChar = s1.pop()) != '(') {  
                    System.out.print(tempChar + " ");  
                    double num1 = s2.pop();  
                    double num2 = s2.pop(); 
                    s3.push(s1.peek());
                    s2.push(calc(num2, num1, tempChar));  
                    if (s1.isEmpty()) {  
                        return "出錯";
                    }  
                }  
            } else if (c == ' ') {  
                    continue; 
            } else {  
                return "出錯"; 
            }  
        }  
        while (!s1.isEmpty()) {  
            tempChar = s1.pop();  
            s3.push(tempChar);  
            double num1 = s2.pop();  
            double num2 = s2.pop();  
            s2.push(calc(num2, num1, tempChar));  
        }  
        double result = s2.pop();  
        if (!s2.isEmpty())  
            return "出錯";
        System.out.print("字尾是:");
        while (!s3.isEmpty()) {
            System.out.print(s3.pop() + " ");
        }
        System.out.println();
        if ((int) result == result)  
            return String.valueOf(((int)result)); 
        else  
            return String.valueOf(result);  
    }

    /**
     * 獲取是double值得最後一位索引
     */
    public static int readDouble(String str, int start) {
        int len = str.length();
        int dotIndex = -1;
        char ch;
        for (int i = start; i < len ; i++) {
            ch = str.charAt(i);
            if(ch == '.') {
                if(dotIndex != -1) 
                    return -1;
                else if(i == len - 1) 
                    return -1;
                else
                    dotIndex = i;
            } else if(!Character.isDigit(ch)) {
                if (dotIndex == -1 || i - dotIndex > 1)
                    return i;
                else
                    return -1;
            }
            else if(i == len - 1) {
                return len;
            }
        }
        return -1;
    }

    /**
     * 計算兩個數的結果
     * 並返回
     */
    private static double calc (double num1, double num2, char op) 
        throws IllegalArgumentException{
        switch (op) {
        case '+':
            return num1 + num2;
        case '-':
            return num1-num2;
        case '*':
            return num1 * num2;
        case '/':
            if (num2 == 0) 
                throw new ArithmeticException("除數不能為0");
            return num1 / num2;
        default:
            return 0;
        }

    }
}