在計算器中輸入表達式,然後得出計算結果,是一個比較常見的過程,對於含有括號的運算表達式的運算順序計算機需要自動識別,這裏就涉及到表達式的轉換。我們手寫或者輸入的都是中綴表達式,比如“1+(2-3)*45+41/(2*10)”,“1+(2-3)*45+41/2*10”。當然也可以支持其它函數表達式比如exp()等。
通常要轉化爲波蘭表達式或者逆波蘭表達式,方便計算機進行運算。也就是說第一步是中綴表達式轉爲(逆)波蘭表達式,第二步計算逆波蘭表達式。
以“1+(2-3)*45+41/2*10”爲例進行算法說明。
中綴表達式轉爲前綴表達式對字符串進行逆向掃描,中綴表達式轉爲後綴表達式對字符串進行順向掃描。
“1+(2-3)*45+41/(2*10)”
前綴:+ + 1 * - 2 3 45 / 41 * 2 10
後綴:1 2 3 - 45 * + 41 2 10 * / +
“1+(2-3)*45+41/2*10”
前綴:+ + 1 * - 2 3 45 / 41 * 2 10
後綴:+ + 1 * - 2 3 45 * / 41 2 10
中綴表達式轉爲前綴表達式
算法步驟:
初始化兩個棧:運算符棧operators和操作數棧operands;
(2) 從右至左掃描中綴表達式;
(3) 遇到操作數時,將其壓入operands;
(4) 遇到運算符時,比較其與operators棧頂運算符的優先級:
(4-1) 如果operators爲空,或棧頂運算符爲右括號“)”,則直接將此運算符入棧;
(4-2) 否則,若優先級比棧頂運算符的較高或相等,也將運算符壓入operators;
(4-3)否則,將operators棧頂的運算符彈出並壓入到operands中,再次轉到(4-1)與operators中新的棧頂運算符相比較;
(5) 遇到括號時:
(5-1) 如果是右括號“)”,則直接壓入operators;
(5-2) 如果是左括號“(”,則依次彈出operators棧頂的運算符,並壓入operands,直到遇到右括號爲止,此時將這一對括號丟棄;
(6) 重複步驟(2)至(5),直到表達式的最左邊;
(7) 將operators中剩餘的運算符依次彈出並壓入operands;
(8) 依次彈出operands中的元素並輸出,結果即爲中綴表達式對應的前綴表達式。
具體過程如下:
中綴表達式轉爲後綴表達式
算法步驟:
與轉換爲前綴表達式相似,遵循以下步驟:
(1) 初始化兩個棧:操作符棧operators和操作數棧operands;
(2) 從左至右掃描中綴表達式;
(3) 遇到操作數時,將其壓入operands;
(4) 遇到運算符時,比較其與operators棧頂運算符的優先級:
(4-1) 如果operators爲空,或棧頂運算符爲左括號“(”,則直接將此運算符入棧;
(4-2) 否則,若優先級比棧頂運算符的高,也將運算符壓入operators(注意轉換爲前綴表達式時是優先級較高或相同,而這裏則不包括相同的情況);
(4-3) 否則,將operators棧頂的運算符彈出並壓入到operands中,再次轉到(4-1)與S1中新的棧頂運算符相比較;
(5) 遇到括號時:
(5-1) 如果是左括號“(”,則直接壓入operators;
(5-2) 如果是右括號“)”,則依次彈出operators棧頂的運算符,並壓入operands,直到遇到左括號爲止,此時將這一對括號丟棄;
(6) 重複步驟(2)至(5),直到表達式的最右邊;
(7) 將S1中剩餘的運算符依次彈出並壓入operands;
具體步驟如下:
Java實現
具體實現採用的雙端隊列替代棧,因爲涉及到逆序問題使用起來更加方便。目前只支持整型數計算
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Stack;
public class Parser {
public static HashMap<String, Integer> priority;
public Parser()
{
priority = new HashMap<>(); //存儲運算符優先級,越大優先級越高
priority.put("+", 2);
priority.put("-", 2);
priority.put("*", 3);
priority.put("/", 3);
}
public static String[] postfixExpression(String s) {
Deque<String> operands = new LinkedList<>();
Deque<String> operators = new LinkedList<>();
for (int i = 0; i < s.length(); i++) {
//遇到數字字符,嘗試往後讀取整個數字
if (s.charAt(i) >= '0' && s.charAt(i) <= '9') {
int j = i, num = s.charAt(i) - '0';
while (j + 1 < s.length() && s.charAt(j + 1) >= '0' && s.charAt(j + 1) <= '9') {
j++;
num = num * 10 + s.charAt(j) - '0';
}
operands.add(String.valueOf(num));
i = j;
} else if (priority.containsKey(String.valueOf(s.charAt(i)))) { //遇到運算符
String op = String.valueOf(s.charAt(i));
//當前運算符優先級比運算符棧棧頂優先級高或者相等 或者棧頂爲括號
if (operators.isEmpty() || operators.peekLast().equals("(") || priority.get(op) > priority.get(operators.peekLast()))
operators.add(op);
else {
while (!operators.isEmpty() && priority.get(operators.peekLast()) >= priority.get(op)) {
operands.add(operators.pollLast());
}
operators.add(op);
}
} else if (s.charAt(i) == '(')
operators.add("(");
else if (s.charAt(i) == ')') {
while (!operators.isEmpty() && !operators.peekLast().equals("("))
operands.add(operators.pollLast());
if (!operators.isEmpty())
operators.pollLast();
}
}
while (!operators.isEmpty()) {
operands.add(operators.pollLast());
}
String[] ans = new String[operands.size()];
int i = 0;
while (!operands.isEmpty())
ans[i++] = operands.pollFirst();
return ans;
}
public static String[] prefixExpression(String s) {
Deque<String> operands = new LinkedList<>();
Deque<String> operators = new LinkedList<>();
for (int i = s.length() - 1; i >= 0; i--) {
//遇到數字字符,嘗試往後讀取整個數字
if (s.charAt(i) >= '0' && s.charAt(i) <= '9') {
int j = i, num = s.charAt(i) - '0', count = 10;
while (j - 1 >= 0 && s.charAt(j - 1) >= '0' && s.charAt(j - 1) <= '9') {
j--;
num = num + count * (s.charAt(j) - '0');
count *= 10;
}
operands.add(String.valueOf(num));
i = j;
} else if (priority.containsKey(String.valueOf(s.charAt(i)))) { //遇到運算符
String op = String.valueOf(s.charAt(i));
//當前運算符優先級比運算符棧棧頂優先級高或者相等 或者棧頂爲括號
if (operators.isEmpty() || operators.peekLast().equals(")") || priority.get(op) >= priority.get(operators.peekLast()))
operators.add(op);
else {
while (!operators.isEmpty() && priority.get(operators.peekLast()) > priority.get(op)) {
operands.add(operators.pollLast());
}
operators.add(op);
}
} else if (s.charAt(i) == '(') {
while (!operators.isEmpty() && !operators.peekLast().equals(")"))
operands.add(operators.pollLast());
operators.pollLast();
} else if (s.charAt(i) == ')')
operators.add(")");
}
while (!operators.isEmpty()) {
operands.add(operators.pollLast());
}
String[] ans = new String[operands.size()];
int i = 0;
while (!operands.isEmpty())
ans[i++] = operands.pollLast();
return ans;
}
public static void main(String[] args) {
Parser p = new Parser();
String s1 = "1+(2-3)*45+41/(2*10)"; // 1 2 3 - 45 * +41 2 / +
String s2="1+(2-3)*45+41/2*10";
String[] ans = p.prefixExpression(s1);
System.out.println(calculateByPrefix(ans));
}
}
得到前綴或者後綴表達式之後計算比較方便,藉助一個棧即可,代碼如下:
//根據後綴表達式計算值
public static int calculateByPostfix(String[] tokens) {
Stack<String> stack = new Stack();
for (String s : tokens) {
if (s.length() == 1) {
char ch = s.charAt(0);
switch (ch) {
case '+': {
int a = Integer.valueOf(stack.pop());
int b = Integer.valueOf(stack.pop());
stack.push(String.valueOf(a + b));
break;
}
case '-': {
int a = Integer.valueOf(stack.pop());
int b = Integer.valueOf(stack.pop());
stack.push(String.valueOf(b - a));
break;
}
case '*': {
int a = Integer.valueOf(stack.pop());
int b = Integer.valueOf(stack.pop());
stack.push(String.valueOf(a * b));
break;
}
case '/': {
int a = Integer.valueOf(stack.pop());
int b = Integer.valueOf(stack.pop());
stack.push(String.valueOf(b / a));
break;
}
default:
stack.push(s);
}
} else
stack.push(s);
}
return Integer.valueOf(stack.pop());
}
//根據前綴表達式計算值
public static int calculateByPrefix(String[] tokens) {
Stack<String> stack = new Stack<>();
for (int i = tokens.length - 1; i >= 0; i--) {
String s = tokens[i];
if (s.length() == 1) {
char ch = s.charAt(0);
switch (ch) {
case '+': {
int a = Integer.valueOf(stack.pop());
int b = Integer.valueOf(stack.pop());
stack.push(String.valueOf(a + b));
break;
}
case '-': {
int a = Integer.valueOf(stack.pop());
int b = Integer.valueOf(stack.pop());
stack.push(String.valueOf(a - b));
break;
}
case '*': {
int a = Integer.valueOf(stack.pop());
int b = Integer.valueOf(stack.pop());
stack.push(String.valueOf(a * b));
break;
}
case '/': {
int a = Integer.valueOf(stack.pop());
int b = Integer.valueOf(stack.pop());
stack.push(String.valueOf(a / b));
break;
}
default:
stack.push(s);
}
} else
stack.push(s);
}
return Integer.valueOf(stack.pop());
}
測試代碼:
public static void main(String[] args) {
Parser p = new Parser();
String s1 = "1+(2-3)*45+41/(2*10)"; // 1 2 3 - 45 * +41 2 / +
String s2 = "1+(2-3)*45+41/2*10";
String[] ansPre1 = p.prefixExpression(s1);
String[] ansPost1=p.postfixExpression(s1);
String[] ansPre2 = p.prefixExpression(s2);
String[] ansPost2=p.postfixExpression(s2);
println(ansPre1);
println(ansPost1);
println(ansPre1);
println(ansPre2);
System.out.println(calculateByPrefix(ansPre1));
System.out.println(calculateByPostfix(ansPost1));
System.out.println(calculateByPrefix(ansPre2));
System.out.println(calculateByPostfix(ansPost2));
}
結果:
+ + 1 * - 2 3 45 / 41 * 2 10
1 2 3 - 45 * + 41 2 10 * / +
+ + 1 * - 2 3 45 / 41 * 2 10
+ + 1 * - 2 3 45 * / 41 2 10
-42
-42
156
156
算法題鏈接
上述算法主要是對字符串的一些處理技巧和經驗。基本使用for循環代替while,另外涉及到字符串前後關係的處理一般會使用棧這個數據結構。
LeetCode--224. Basic Calculator
class Solution {
public int calculate(String s) {
Stack<Integer> stack=new Stack<>();
int sign=1,res=0;int num=0;
for(int i=0;i<s.length();i++)
{
char c=s.charAt(i);
switch(c)
{
case '+':
res += sign * num;
num=0;
sign=1;
break;
case '-':
res += sign * num;
num=0;
sign=-1;
break;
case ' ':
break;
case '(':
stack.push(res);
stack.push(sign);
res=0;
sign=1;
break;
case ')':
res+=sign*num;
num = 0;
res *= stack.pop(); //stack.pop() is the sign before the parenthesis
res += stack.pop();
break;
default:
num = 10 * num + (c - '0');
}
}
if(num != 0)
res += sign * num;
return res;
}
}
LeetCode--227. Basic Calculator II
class Solution {
public int calculate(String s) {
int num=0;
char sign='+';
Stack<Integer> stack=new Stack<>();
for(int i=0;i<s.length();i++){
char c=s.charAt(i);
if(Character.isDigit(c))
num=num*10+c-'0';
if((!Character.isDigit(c) && c!=' ') || i==s.length()-1){
if(sign=='+')
stack.push(num);
if(sign=='-')
stack.push(-num);
if(sign=='*')
stack.push(stack.pop()*num);
if(sign=='/')
stack.push(stack.pop()/num);
num=0;
sign=c;
}
}
int sum=0;
while(!stack.isEmpty())
sum+=stack.pop();
return sum;
}
}
LeetCode--282. Expression Add Operators
class Solution {
public static List<String> ret;
public List<String> addOperators(String num, int target) {
ret=new LinkedList<>();
backtrace(num,new StringBuilder(),target,0,0,0);
return ret;
}
public static void backtrace(String num,StringBuilder sb,int target,int start,long pre,long sum){
if(start==num.length())
{
if(sum==target)
ret.add(sb.toString());
return;
}
for(int i=start;i<num.length();i++){
if(num.charAt(start)=='0' && i>start)
break;
long val=Long.valueOf(num.substring(start,i+1));
int len=sb.length();
if(start==0){
backtrace(num,sb.append(val),target,i+1,val,sum+val);
sb.setLength(len);
}else{
backtrace(num,sb.append('+').append(val),target,i+1,val,sum+val);
sb.setLength(len);
backtrace(num,sb.append('-').append(val),target,i+1,-val,sum-val);
sb.setLength(len);
backtrace(num,sb.append('*').append(val),target,i+1,pre*val,sum-pre+pre*val);
sb.setLength(len);
}
}
}
}
LeetCode--150. Evaluate Reverse Polish Notation
class Solution {
public int evalRPN(String[] tokens) {
Stack<String> stack=new Stack();
for(String s:tokens)
{
if(s.length()==1)
{
char ch=s.charAt(0);
switch(ch)
{
case '+':
{
int a=Integer.valueOf(stack.pop());
int b=Integer.valueOf(stack.pop());
stack.push(String.valueOf(a+b));
break;
}
case '-':
{
int a=Integer.valueOf(stack.pop());
int b=Integer.valueOf(stack.pop());
stack.push(String.valueOf(b-a));
break;
}
case '*':
{
int a=Integer.valueOf(stack.pop());
int b=Integer.valueOf(stack.pop());
stack.push(String.valueOf(a*b));
break;
}
case '/':
{
int a=Integer.valueOf(stack.pop());
int b=Integer.valueOf(stack.pop());
stack.push(String.valueOf(b/a));
break;
}
default:
stack.push(s);
}
}
else
stack.push(s);
}
return Integer.valueOf(stack.pop());
}
}
LeetCode--394. Decode String
class Solution {
public String decodeString(String s) {
int i=0;
Stack<Character> stack=new Stack<>();
while(i<s.length())
{
if(s.charAt(i)!=']')
{
stack.push(s.charAt(i));
}
else
{
StringBuilder tsb=new StringBuilder();
while(!stack.isEmpty() && stack.peek()!='[')
tsb.append(stack.pop());
stack.pop();
int frequency=0,pos=1;
while(!stack.isEmpty() && stack.peek()>='0' && stack.peek()<='9')
{
frequency=pos*(stack.pop()-'0')+frequency;
pos*=10;
}
for(int j=0;j<frequency;j++)
{
for(int k=tsb.length()-1;k>=0;k--)
stack.push(tsb.charAt(k));
}
}
i++;
}
char[] ret=new char[stack.size()];
for(int m=stack.size()-1;m>=0;m--)
ret[m]=stack.pop();
return new String(ret);
}
}
參考:
https://zh.wikipedia.org/wiki/%E6%B3%A2%E5%85%B0%E8%A1%A8%E7%A4%BA%E6%B3%95