對於一個字符串類型的表達式,實現基礎的加減乘除運算
String expression = "8/2+2*5-4";
使用棧來實現這個表達式的解析和運算
實現思路:
1,通過一個index來遍歷我們的表達式
2,如果發現是一個數字,判斷下一位是不是運算符,如果是,把數字入數棧。如果不是,繼續掃描,拼接數字
3,如果發現是一個運算符,判斷符號棧是否爲空,如果爲空,直接入符號棧。如果不爲空,就跟符號棧中棧頂的符號進行比較,如果優先級小於等於棧頂的符號,就需要從數棧中pop出兩個數,從符號棧中pop出一個符號進行運算(注意運算時候的順序,在進行減和除的時候,後面出來的數在前),將得到的結果入數棧,將當前掃描到的符號入符號棧。如果當前符號的優先級大於符號棧中棧頂的符號,就直接把當前符號入符號棧。
4,當表達式掃描完畢,就順序的從數棧和符號棧中pop出相應的數和符號,進行運算。
5,最後數棧中只有一個數,就是表達式的結果。
代碼實現:
public class StackCompute {
@Test
public void main(){
//表達式
String expression = "8/2+2*5-4";
//創建兩個棧,一個數棧,一個符號棧
Stack<Integer> numStack = new Stack<>();
Stack<Character> operStack = new Stack<>();
//定義需要的變量
int index = 0;//用於掃描表達式
int num1 = 0;
int num2 = 0;
int oper = 0;//符號
int result = 0;//運算結果
char ch = ' ';//將每次掃描到char保存到ch
String keepNum = "";//拼接數
//開始while循環掃描表達式
while(true){
//依次得到表達式中的每一個字符
ch = expression.substring(index,index+1).charAt(0);
//判斷ch是什麼,然後做相應的處理
if(isOper(ch)){//如果是一個運算符
//判斷當前符號棧是否爲空
if(!operStack.isEmpty()){
/*
如果符號棧有操作符,就進行比較,如果當前的操作符的優先級小於或者等於棧中的操作符,就需要從數棧中pop出兩個數
在從符號棧中pop出一個符號,進行運算,將得到結果,入數棧,然後將當前掃描到的操作符入符號棧
*/
if (priority(ch) <= priority(operStack.peek())) {
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
result = cal(num1,num2,oper);
//把運算結果入數棧
numStack.push(result);
//當前符號入符號棧
operStack.push(ch);
}else{
//如果當前符號的優先級大於棧中符號的優先級,就直接入符號棧
operStack.push(ch);
}
}else{
//如果爲空,直接入棧
operStack.push(ch);
}
}else{
//如果是數,入數棧
//處理多位數
keepNum += ch;
//如果是表達式的最後一位。直接入數棧
if(index == expression.length()-1){
numStack.push(Integer.parseInt(keepNum));
}else{
//如果不是最後一位,則判斷後一位是不是字符,如果是字符,則入棧,反之,則繼續掃描
if(isOper(expression.substring(index+1,index+2).charAt(0))){
numStack.push(Integer.parseInt(keepNum));
keepNum = "";//清空keepNum
}
}
}
index++;
if(index >= expression.length()){
break;
}
}
//當表達式掃描完畢,就順序的從數棧和符號棧中pop出相應的數和符號,並運行。
while(true){
//如果符號棧爲空,表示已經計算到最後的結果,數棧中此時應該只有一個數字,就是結果
if(operStack.isEmpty()){
break;
}
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
result = cal(num1,num2,oper);
//把運算結果入數棧
numStack.push(result);
}
System.out.println(numStack.pop());
}
//判斷是否是一個運算符,目前僅限於加減乘除
boolean isOper(int ch){
return ch == '+' || ch == '-' || ch == '*' || ch == '/';
}
//判斷優先級
int priority(int ch){
if(ch == '*' || ch == '/'){
return 1;
}else{
return 0;
}
}
//計算方法
int cal(int num1,int num2,int oper){
int result = 0;
switch (oper){
case '+': result = num1 + num2;break;
case '-': result = num2 - num1;break;//注意順序,後出來的數做被減數
case '*': result = num1 * num2;break;
case '/': result = num2 / num1;break;//注意順序,後出來的數做被除數
}
return result;
}
}
在計算機中,前綴表達式(波蘭表達式)或者後綴表達式(逆波蘭表達式)比中綴表達式更好解析。
前綴表達式
後綴表達式
後綴表達式的求值:
public class PolandNotation {
public static void main(String[] args) {
//定義一個逆波蘭表達式
//5*7-8+3+9/3 => 5 7 * 8 - 3 + 9 3 / +
//爲了方便,我們吧逆波蘭表達式的數字和符號之間使用空格隔開
String suffixExpression = "5 7 * 8 - 3 + 9 3 / +";
/**
* 思路
* 1,先將表達式放入一個ArrayList中
* 2,遍歷ArrayList配合棧完成計算
*/
List<String> list = getListString(suffixExpression);
System.out.println(calculate(list));
}
//將表達式轉換成集合
public static List<String> getListString(String suffixExpression) {
List<String> list = new ArrayList<String>();
String[] split = suffixExpression.split(" ");
for (String ele : split) {
list.add(ele);
}
return list;
}
/**
* 完成對逆波蘭表達式的計算
* 思路分析:
* 1,從左至右掃描,將3和4壓入堆棧;
* 2,遇到+運算符,因此彈出4和3 ( 4爲棧頂元素, 3爲次項元素),計算出3+4的值,得7,再將7入棧;
* 3,將5入棧;
* 4,接下來是*運算符,因此彈出5和7,計算出7*5=35,將35入棧;
* 5,將6入棧;
* 6,最後是-運算符,計算出35-6的值,即29,由此得出最終結果;
*
* @param ls
* @return
*/
public static int calculate(List<String> ls) {
//創建一個棧
Stack<String> stack = new Stack<>();
for (String item : ls) {
if (item.matches("\\d+")) {//如果是數字
stack.push(item);//入棧
} else {
//pop出兩個數進行運算後再入棧
//因爲需要用後面彈出的數做被減數或被除數,所以爲了方便,這裏第一個彈出的數命名爲num2
int num2 = Integer.parseInt(stack.pop());
int num1 = Integer.parseInt(stack.pop());
int result = 0;
if (item.equals("+")) {
result = num1 + num2;
} else if (item.equals("-")) {
result = num1 - num2;
} else if (item.equals("/")) {
result = num1 / num2;
} else if (item.equals("*")) {
result = num1 * num2;
}else{
throw new RuntimeException("運算符有誤");
}
//把結果入棧
stack.push(result+"");
}
}
//最後留在棧中的數就是運算結果
return Integer.parseInt(stack.pop());
}
}
那麼問題來了,如何把一箇中綴表達式轉成一個後綴表達式呢?
思路分析:
1,初始化兩個棧,運算符棧s1和存儲中間結果棧s2
2,從左至右掃描中綴表達式
3,遇到數字時,將其壓入s2
4,遇到運算符時,比較其與s1棧頂運算符的優先級
4.1,如果s1爲空,或棧頂運算符爲左括號,則直接將此運算符入s1
4.2,如果運算符比棧頂的運算符優先級高,也將運算符入s1
4.3,如果運算符比棧頂的運算符優先級低,將s1的運算符彈出壓入s2,再次轉到4.1中與新的棧頂運算符比較
5,遇到括號時
5.1,如果是左括號,直接壓入s1
5.2,如果是右括號,則依次彈出s1棧頂的運算符壓入s2,直到遇到左括號爲止,此時將這一對括號丟棄
6,重複步驟2至5,直到表達式的最右邊
7,將s1中剩餘的運算符依次彈出並壓入s2,
8,依次彈出s2中的元素並輸出,結果的逆序即爲中綴表達式對應的後綴表達式
代碼實現:
public static void main(String[] args) {
List<String> list = toInfixExpressionList("1+((2+3)*4)-5");
System.out.println(list);
List<String> result = parseSuffixExpreesionList(list);
System.out.println(result);
}
/**
* 將中綴表達式轉換成對應的list
*
* @param s
* @return
*/
public static List<String> toInfixExpressionList(String s) {
//定義一個list,存放中綴表達式對應的內容
List<String> list = new ArrayList<>();
int i = 0;//這是一個指針,用於遍歷中綴表達式字符串
String str;//對多位數的拼接
char c;//每遍歷到一個字符就放入到c中
do {
//如果c是一個非數字,加入到list中 0-9對應的Asall碼是48-57
if ((c = s.charAt(i)) < 48 || (c = s.charAt(i)) > 57) {
list.add(c + "");
i++;
} else {//如果是一個數字
str = "";
while (i < s.length() && (c = s.charAt(i)) > 48 && (c = s.charAt(i)) < 57) {
str += c;//拼接多位數
i++;
}
list.add(str);
}
} while (i < s.length());
return list;
}
/**
* 中綴表達式的list轉後綴表達式list
*
* @param list
* @return
*/
public static List<String> parseSuffixExpreesionList(List<String> list) {
//定義兩個棧
Stack<String> s1 = new Stack<>();//符號棧
/**
* 因爲s2這個棧,在整個轉換過程中,沒有pop操作,而且後面還要逆序輸出
* 就比較麻煩,所以使用ArrayList代替
*/
List<String> s2 = new ArrayList<>();//存儲中間結果的list
//遍歷list
for (String item : list) {
//如果是一個數字,加入s2
if (item.matches("\\d+")) {
s2.add(item);
} else if (item.equals("(")) {
s1.push(item);
}else if(item.equals(")")){
//如果是右括號,則依次彈出s1棧頂的運算符壓入s2,直到遇到左括號爲止,此時將這一對括號丟棄
while(!s1.peek().equals("(")){
s2.add(s1.pop());
}
s1.pop();//將左括號彈出,消除一堆小括號
}else{
//如果運算符比棧頂的運算符優先級低,將s1的運算符彈出壓入s2,再次轉到4.1中與新的棧頂運算符比較
while(s1.size() != 0 && getValue(s1.peek()) >= getValue(item)){
s2.add(s1.pop());
}
//然後將item入棧
s1.push(item);
}
}
//將s1剩餘的運算符依次彈出並加入s2
while(s1.size() != 0){
s2.add(s1.pop());
}
return s2;
}
/**
* 判斷運算符優先級
* @param oper
* @return
*/
public static int getValue(String oper){
int result = 0;
switch (oper){
case "+" :
result = 1;
break;
case "-" :
result = 1;
break;
case "*" :
result = 2;
break;
case "/":
result = 2;
break;
default:
System.out.println("不存在或不支持該運算符");
break;
}
return result;
}
如果需要加入小數點,可以把運算時候的數據類型改成Double,然後加入判斷小數點