問題概述
我們完成一個逆波蘭計算器,要求完成如下任務
- 輸入一箇中綴表達式,轉成後綴表達式(逆波蘭表達式),使用stack計算結果
- 要求支持小括號,和多位整數,我們暫時不考慮小數問題
逆波蘭表達式書寫
逆波蘭表達式(Reverse Polish notation, RPN,或逆波蘭記法),也叫後綴表達式,運算符寫在數值的後面
爲什麼要使用逆波蘭表達式呢
實現逆波蘭表達式其實並不是很難,但是爲什麼要將看似簡單的中綴表達式轉成後綴表達式呢?
其實中綴表達式只是對人類而言是簡單的,然而對於計算機來說,中綴表達式是一個非常複雜的結構。而後綴表達式對於計算機是一個很容易理解的結構。因爲計算即執行的是一個棧結構,執行先進後出的順序
轉後綴表達式步驟
- 先準備兩個棧S1,S2,分別用於存儲符號和存儲數值,從中綴表達式的最左邊開始取值。
- 當取出的是一個操作數,則直接送入數值棧S2。
- 當取出是一個操作符時,如果棧頂的爲空則直接送入操作符棧S1,如果當前操作符級別(不包括括號運算符)大於棧頂操作符時,也直接壓入符號棧S1;當前操作符小於棧頂操作符時,彈出符號棧中的符號送入數值棧S2直至當前操作符級別大於棧頂操作符,將當前操作符壓入操作符棧,當棧頂爲(括號時當前操作符直接壓入符號棧S1中。
- 當取出操作符爲(括號時,直接壓入操作符棧中
- 當取出操作符爲)括號時,彈出操作符直至棧頂符號爲(,最後將(也彈出。
- 重複之前的動作,直至遍歷完算式
- 最後逆序打印我們的數值棧S2即可得到我們的逆波蘭表達式。
舉個例子
將1 + ((2 + 3)* 4)- 5 轉成後綴表達式爲1 2 3 + 4 * 5 - +
取出的操作數 | S1棧(棧底 --> 棧頂) | S2棧(棧底 --> 棧頂) | 步驟說明 |
---|---|---|---|
1 | 空 | 1 | 將數值1壓入數值棧中 |
+ | + | 1 | 符號棧中爲空,直接壓入符號棧 |
( | + ( | 1 | 操作符爲(直接壓入符號棧中 |
( | + ( ( | 1 | 操作符爲( 直接壓入符號棧中 |
2 | + ( ( | 1 2 | 取出操作數爲數值直接壓入數值棧中 |
+ | + ( ( + | 1 2 | 符號棧的棧頂爲(括號直接壓入符號棧中 |
3 | + ( ( + | 1 2 3 | 取出操作數爲數值直接壓入數值棧中 |
) | + ( | 1 2 3 + | 取出操作符爲)彈出符號棧中的符號直到彈出最近的(,並將彈出的符號壓入數值棧中 |
* | + ( * | 1 2 3 + | 取出操作符爲*並且當前操作符級別大於棧頂操作符級別,直接壓入符號棧 |
4 | + ( * | 1 2 3 + 4 | 取出操作數爲數值直接壓入數值棧中 |
) | + | 1 2 3 + 4 * | 取出操作符爲)彈出符號棧中的符號直到彈出最近的(,並將彈出的符號壓入數值棧中 |
- | + - | 1 2 3 + 4 * | 取出操作符爲*並且當前操作符級別大於棧頂操作符級別,直接壓入符號棧 |
5 | + - | 1 2 3 + 4 * 5 | 取出操作數爲數值直接壓入數值棧中 |
空 | 空 | 1 2 3 + 4 * 5 - + | 清空符號棧S1 |
使用逆波蘭表達式計算結果
- 創建一個棧S1用於存儲數值,從左到右遍歷逆波蘭表達式(後綴表達式)
- 取出數爲數值壓入棧中
- 取出數爲符號時,彈出棧中兩個數值進行計算,並將計算結果壓入數值棧中。
- 重複2,3部直至到後綴表達式底部
取出的數值 | 棧(棧底 – > 棧頂) | 步驟說明 |
---|---|---|
1 | 1 | 操作數爲數值壓入棧中 |
2 | 1 2 | 操作數爲數值壓入棧中 |
3 | 1 2 3 | 操作數爲數值壓入棧中 |
+ | 1 5 | 操作數爲符號,彈出兩個數值進行運算,將運算結果壓入棧中 |
4 | 1 5 4 | 操作數爲數值壓入棧中 |
* | 1 20 | 操作數爲符號,彈出兩個數值進行運算,將運算結果壓入棧中 |
5 | 1 20 5 | 操作數爲數值壓入棧中 |
- | 1 15 | 操作數爲符號,彈出兩個數值進行運算,將運算結果壓入棧中 |
+ | 16 | 操作數爲符號,彈出兩個數值進行運算,將運算結果壓入棧中 |
代碼實現簡單計算器功能
import java.util.List;
import java.util.ArrayList;
public class Calculator {
public static void main(String[] args) {
String express = "1+((2+3)*4)-5":
List<String> suffixExpression = SuffixExpression.toSuffixExpression(list);
int calculate = SuffixExpression.calculate(suffixExpression);
System.out.println(calculate);
}
/**
* 將一個算式解析成一個個元素
* @param 需要截取的字符串
*/
public static List<String> strToList(String str) {
if (str == null || str.length() <= 0) {
throw new IllegalArgumentException();
}
List<String> list = new ArrayList<String>();
for (int i = 0;i < str.length();i++) {
if (str.charAt() < 48 || str.charAt(i) > 57) {
list.add("" + str.charAt(i));
} else {
String sub = "":
while (i < str.length() && str.charAt(i) >= 48 && str.charAt(i) <= 57) {
sub += str.charAt(i);
list.add(sub);
i++;
}
}
}
return list;
}
/**
* 將中綴表達式轉逆波蘭表達式(後綴表達式)
* @param 需要轉的中綴表達式
*/
public static List<String> toSuffixExpression(List<String> list) {
if (list == null || list.isEmpty()) {
throw new IllegalArgumentException();
}
// 用於存放操作符的
Stack<String> stack = new Stack<String>();
// 用於存放數值,由於最後我們是需要逆序輸出數值棧,所以這裏直接用集合替代棧,可以免去逆序打印
List<String> suffix = new ArrayList<String>();
for (String str:list) {
if (str.matches("\\d")) {
suffix.add(str);
} else if (str.equals("(")) {
stack.push(str);
} else if (str.equals(")")) {
while (!stack.peek().equals("(")) {
suffix.add(stack.pop());
}
stack.pop();
} else {
while (stack.size() > 0 && getValue(str) < getValue(stack.peek())) {
suffix.add(stack.pop());
}
stack.push(str);
}
}
while (stack.size() > 0){
suffix.add(stack.pop());
}
}
/**
* 根據逆波蘭表達式計算結果
* @param 需要計算的逆波蘭表達式
*/
public static int calculator(List<String> list) {
if (list == null || list.isEmpty()) {
throw new IllegalArgumentException();
}
Stack<String> stack = new Stack<String>();
for (String str:list) {
if (str.matches("\\d")) {
stack.push(str);
} else {
int num1 = Integer.parseInt(stack.pop());
int num2 = Integer.parseInt(stack.pop());
if (str.equals("+")){
stack.push(String.valueOf(num1+num2));
}else if (str.equals("-")){
stack.push(String.valueOf(num2-num1));
}else if (str.equals("*")){
stack.push(String.valueOf(num1*num2));
}else if (str.equals("/")){
stack.push(String.valueOf(num2/num1));
}
}
}
return Integer.parseInt(stack.pop());
}
/**
* 計算輸入字符的優先級
* @param 需要判斷的字符
*/
public static int getValue(String str){
int result;
switch (str){
case "+":
result = 1;
break;
case "-":
result = 1;
break;
case "*":
result = 2;
break;
case "/":
result = 2;
break;
default:
result = 0;
break;
}
return result;
}
}
代碼寫完,小弟只是能力有限,難免有漏洞,歡迎各路大神指點評論!!!