【Algorithm】逆波蘭表示式 Java實現
簡介
逆波蘭表示法(Reverse Polish notation,RPN,或逆波蘭記法),是一種是由波蘭數學家揚·武卡謝維奇1920年引入的數學表示式方式,在逆波蘭記法中,所有操作符置於運算元的後面,因此也被稱為字尾表示法。逆波蘭記法不需要括號來標識操作符的優先順序。
逆波蘭表示式可以用於表示式轉換,如數學公式轉換計算,很早之前做過一個計算器,但是解析公式 到時候感到萬分頭疼,今天看到逆波蘭表示式這個東西,不禁感慨:
果然,資料結構學不好是得吃大虧。
思路
對於常規的數學表示式,我們要做的是根據其優先順序計算結果(小學生都知道)。
程式設計實現的話,其實也很簡單,兩個棧即可實現,姑且把一個棧叫做 operators
output
,儲存最終的表示式。
就三個要點:
- 數字直接入
output
- 運算子要與
operators
棧頂比較,優先順序大則入棧,小於或等於則operators
出棧後再入棧 operators
棧頂若是(
則無條件入棧
下面舉的這個例子,只針對簡單 +
-
*
/
(
)
,別的實現,如小數,平方等操作,原理都差不多,可以自行擴充套件。
對這個a*(b-c*d)+e
式子來說,轉成波蘭表示式後應該是這樣子的:abcd*-*e+
1.讀到a
,將a
壓入output
2.讀到*
,operators
棧為空,直接將*
壓入operators
3.讀到(
優先順序最高,無條件壓入operators
4.讀到b
,將b
壓入output
5.讀到-
,因為有(
的存在,無視比之前的*
或(
優先順序低
6.讀到c
,將c
壓入output
7.讀到*
,因為比+
的優先順序高,壓入operators
8.讀到d
,將d
壓入output
9.讀到)
,將operators
中(
之後的所有運算子彈出並壓入output
10.讀到+
,將operators
所有棧頂優先順序比+
的運算子彈出,最後將+
壓入operators
11.讀到e
,將e
壓入output
12.表示式讀取完,將output
所有運算子壓入output
實現
以下程式碼是Java的實現,字串解析和逆波蘭表示式解析放在一起,但還是根據上面的思路來解決。
import java.util.Stack;
public class ReversePolishNotation {
public static void main(String[] args) {
//測試用例
//String str = "1+2*3-4*5-6+7*8-9"; //123*+45*-6-78*+9-
String str = "a*(b-c*d)+e-f/g*(h+i*j-k)"; // abcd*-*e+fg/hij*+k-*-
//String str = "6*(5+(2+3)*8+3)"; //6523+8*+3+*
//String str = "a+b*c+(d*e+f)*g"; //abc*+de*f+g*f
Stack<Character> operators = new Stack<>(); //運算子
Stack output = new Stack(); //輸出結果
rpn(operators, output, str);
System.out.println(output);
}
public static void rpn(Stack<Character> operators, Stack output, String str) {
char[] chars = str.toCharArray();
int pre = 0;
boolean digital; //是否為數字(只要不是運算子,都是數字),用於擷取字串
int len = chars.length;
int bracket = 0; // 左括號的數量
for (int i = 0; i < len; ) {
pre = i;
digital = Boolean.FALSE;
//擷取數字
while (i < len && !Operator.isOperator(chars[i])) {
i++;
digital = Boolean.TRUE;
}
if (digital) {
output.push(str.substring(pre, i));
} else {
char o = chars[i++]; //運算子
if (o == '(') {
bracket++;
}
if (bracket > 0) {
if (o == ')') {
while (!operators.empty()) {
char top = operators.pop();
if (top == '(') {
break;
}
output.push(top);
}
bracket--;
} else {
//如果棧頂為 ( ,則直接新增,不顧其優先順序
//如果之前有 ( ,但是 ( 不在棧頂,則需判斷其優先順序,如果優先順序比棧頂的低,則依次出棧
while (!operators.empty() && operators.peek() != '(' && Operator.cmp(o, operators.peek()) <= 0) {
output.push(operators.pop());
}
operators.push(o);
}
} else {
while (!operators.empty() && Operator.cmp(o, operators.peek()) <= 0) {
output.push(operators.pop());
}
operators.push(o);
}
}
}
//遍歷結束,將運算子棧全部壓入output
while (!operators.empty()) {
output.push(operators.pop());
}
}
}
enum Operator {
ADD('+', 1), SUBTRACT('-', 1),
MULTIPLY('*', 2), DIVIDE('/', 2),
LEFT_BRACKET('(', 3), RIGHT_BRACKET(')', 3); //括號優先順序最高
char value;
int priority;
Operator(char value, int priority) {
this.value = value;
this.priority = priority;
}
/**
* 比較兩個符號的優先順序
*
* @param c1
* @param c2
* @return c1的優先順序是否比c2的高,高則返回正數,等於返回0,小於返回負數
*/
public static int cmp(char c1, char c2) {
int p1 = 0;
int p2 = 0;
for (Operator o : Operator.values()) {
if (o.value == c1) {
p1 = o.priority;
}
if (o.value == c2) {
p2 = o.priority;
}
}
return p1 - p2;
}
/**
* 枚舉出來的才視為運算子,用於擴充套件
*
* @param c
* @return
*/
public static boolean isOperator(char c) {
for (Operator o : Operator.values()) {
if (o.value == c) {
return true;
}
}
return false;
}
}
參考
資料結構與演算法分析-Java語言描述(P67)