一個算數表示式的字首中綴和字尾(可進行負數運算)
一個算數表示式的字首中綴和字尾
目錄
1. 關於字首、中綴和字尾
- 中綴表示式:就是我們常見的算數表示式,有優先順序和括號,例如:3+4*(4+5 )。這個對於我們來說很好理解,但是對於計算機來說就比較麻煩。
- 字首表示式:字首表示式是一種沒有括號的算術表示式,與中綴表示式不同的是,其將運算子寫在前面,運算元寫在後面。為紀念其發明者波蘭數學家Jan Lukasiewicz,字首表示式也稱為“波蘭式”。例如,- 1 + 2 3,它等價於1-(2+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;
}
}
}