文章目录
中缀表达式
中缀表达式(或中缀记法)是一个通用的算术或逻辑公式表示方法, 操作符是中缀形式处于操作数的中间(例:3 + 4,3 + 5 * 4 - 2),中缀表达式是人们常用的算术表示方法。
如何编写一个程序,直接指向计算中缀表达式呢?下面我提供两种方法。
中缀表达式的计算
利用双栈来实现中缀表达式的计算
中缀表达式以字符串的显示给出,例:“30 - 2 * 22 - 31 * 1 + 100 - 20 - 1”;
思路如下:
- 创建两个栈,
数字栈(numStack)
,和字符栈(operStack)
。 - 通过一个下标
index
来遍历中缀表达式。 - 遇到数字,直接入数字栈。
- 遇到字符,分为三种情况:
(1)若符号栈为空,直接入符号栈。
(2)若该字符运算优先级大于字符栈栈顶元素,直接入符号栈。
(3)若该字符运算优先级小于或者等于字符栈栈顶元素,那么需要从数字栈栈顶弹出两个数字,符号栈栈顶弹出一个字符,进行运算,然后将运算结果加入数字栈。此时,需要继续比较index指向的字符与栈顶字符优先级,重复(2)(3)操作,直到该字符运算优先级大于字符栈栈顶元素,直接入符号栈。 - 当表达式遍历完之后,就顺序从字符栈和数栈弹出元素进行计算,并将结果存入数字栈,直到数字栈只有一个元素,该元素就是表达式的运算结果。
注意:
- 要注意多为数的情况。
- 表达式中可能会有空格,遇到空格时要直接跳过。
- 这里没有考虑有括号的情况,以及表达式开头是负数的情况,后面会考虑。
代码实现:
public class Infix { //判断是否是运算符 public static boolean isOper(char ch){ return ch == '+' || ch == '-' || ch == '*' || ch == '/'; } //判断运算符优先级 public static int priority(char oper){ if(oper == '*' || oper == '/') return 1; else if(oper == '+' || oper == '-') return 0; else return -1; } //进行运算 public static int operation(int num1 , int num2, char oper){ switch (oper){ case '+': return num1 + num2; case '-': return num2 - num1; case '*': return num2 * num1; case '/': return num2 / num1; default: return -1; } } public static int evaluate(String s){ //用来保存遍历到的字符 char ch = ' '; //后面用来保存运算结果 int rst = 0; //遍历字符串的下标 int index = -1; //遇到多位数时,用来拼接字符的字符串 String keepNum = ""; Deque<Integer> numStack = new LinkedList<>(); Deque<Character> operStack = new LinkedList<>(); while(true){ index++; //字符串遍历结束,循环终止 if(index >= s.length()) break; //获得下标处的字符串 ch = s.charAt(index); //遇到空格直接跳过 if(ch == ' ') continue; //ch是数字,直接入栈 if(!isOper(ch)){ int i = index; while(i != s.length() && !isOper(s.charAt(i)) && s.charAt(i) != ' '){ keepNum += s.charAt(i); i++; } index = i - 1; numStack.push(Integer.parseInt(keepNum)); keepNum = ""; } //ch不是数字 else{ //运算符栈为空,直接入栈 if(operStack.isEmpty()){ operStack.push(ch); } //运算符栈不为空 else{ //若运算符栈栈顶元素优先级大于或等于ch优先级,数栈出两个元素, // 运算符栈出一个元素,进行运算,并将结果入栈,然后将当前运算符入栈 while(!operStack.isEmpty() && priority(operStack.peek()) >= priority(ch)){ rst = operation(numStack.pop() ,numStack.pop() ,operStack.pop()); numStack.push(rst); } //若栈顶运算符优先级小于ch,直接入栈 operStack.push(ch); } } } //当表达式遍历完之后,就顺序从字符栈和数栈弹出元素进行计算, // 并将结果存入数字栈,直到数字栈只有一个元素,该元素就是表达式的运算结果。 while(!operStack.isEmpty()){ rst = operation(numStack.pop(),numStack.pop(),operStack.pop()); numStack.push(rst); } return numStack.pop(); } public static void main(String[] args) { System.out.println(evaluate1("20 -3 *4 /3+2")); } }
利用单栈实现中缀表达式计算
思路如下:
- 创建一个栈,用来存储数字。
- 使用循环来遍历表达式,下标为 i 。
- 定义一个
flag
,初始时赋值为0;当遇到' * '
时,flag置为1
;遇到' / '
时,flag置为2
;遇到' - '
时 ,flag置为3
;遇到' + '
,flag置为0
。 - 遇到数字时:
(1)若flag为0,将该数字直接入栈。
(2)若flag为1,将栈顶数字弹出
,与该数子进行乘法
运算,结果入栈。
(3)若flag为2,将栈顶数字弹出
,做被除数
,与该数字进行除法运算
,结果入栈。
(4)若flag为3,将该数字变为它的相反数
,然后入栈。 - 最后将栈中数字依次弹出,并且相加,结果就是表达式结果。
代码如下:
public class Infix { static int evaluate(String s){ char ch = ' '; int rst = 0; int index = -1; //在判断多位数时,用于字符串的拼接 String str = ""; int flag = 0; Stack<Integer> st = new Stack<>(); for(int i = 0; i < s.length(); i++){ ch = s.charAt(i); if(ch == ' ') continue; //ch是数字 if(ch >= '0' && ch <= '9'){ //判断多位数情况 str = ch + ""; int j = i + 1; while(j < s.length() && s.charAt(j) >= '0' && s.charAt(j) <='9'){ str += s.charAt(j); j++; } i = j - 1; rst = Integer.parseInt(str); if(flag == 1){ rst *= st.pop(); } else if(flag == 2){ rst =st.pop() / rst; } else if(flag == 3){ rst *= -1; } st.push(rst); flag = 0; } //ch是运算符 else{ if(ch == '*') flag = 1; else if(ch == '/') flag = 2; else if(ch == '-') flag = 3; else flag = 0; } } //将栈里面的数字求和 while(st.size() != 1){ st.push(st.pop() + st.pop()); } return st.pop(); } public static void main(String[] args) { System.out.println(evaluate("20 -3 *4 /3+2")); } }
后缀表达式
后缀表达式,又称逆波兰式,指的是不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行(不再考虑运算符的优先规则)。
例:(3 + 4)* 5 - 6 ===> 3 4 + 5 * 6 -
后缀表达式的计算
思路如下:
从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次项元素和栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果。
例如: (3+4)X5-6对应的前缀表达式就是34+5 X 6-,针对后缀表达式求值步骤如下:
- 从左至右扫描,将3和4压入堆栈;
- 遇到+运算符,因此弹出4和3 (4为栈项元素,3为次顶元素),计算出3+4的、值,得7,再将7入栈;
- 将5入栈;
- 接下来是X运算符,因此弹出5和7,计算出7X5=35,将35入栈;
- 将6入栈;
- 最后是 - 运算符,计算出35-6的值,即29, 由此得出最终结果;
代码如下:
注意:后缀表达式以List的形式给出。
//后缀表达式求值 public static int evaluate(List<String> tokens){ Stack<Integer> Stack = new Stack<>(); String s = ""; int rst = 0; for(int i = 0; i < tokens.size(); i++){ s = tokens.get(i); //如果是运算符 if(isOpear(s)){ rst = operation(Stack.pop(),Stack.pop(),s); Stack.push(rst); } else{ Stack.push(Integer.parseInt(s)); } } return Stack.pop(); }
中缀表达式转后缀表达式
思路如下:
与转换为前缀表达式相似,步骤如下:
- 初始化两个栈:运算符栈s1和储存中间结果的栈s2;
- 从左至右扫描中缀表达式;
- 遇到操作数时,将其压s2;
- 遇到运算符时,比较其与 s1 栈顶运算符的优先级:
(1) 如果 s1 为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
(2) 否则,若优先级比栈顶运算符的高,也将运算符压入 s1;
(3) 否则,将 s1 栈顶的运算符弹出并压入到 s2 中,再次转到 4 - (1)与s1中新的栈顶运算符相比较; - 遇到括号时:
(1) 如果是左括号“(”,则直接压入 s1;
(2) 如果是右括号“)”,则依次弹出 s1 栈顶的运算符,并压入 s2 ,直到遇到左括号为止,此时将这一对括号丢弃; - 重复步骤 2 至 5 ,直到表达式的最右边;
- 将 s1 中剩余的运算符依次弹出并压入 s2;
- 依次弹出 s2 中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式。
注意:
- 同样要考虑多位数的情况,以及表达式中的空格。
- 要考虑负数开头的情况,如 :-1 - 2;我们可以判断一下,若表达式中第一个有效字符为负号时,可以往 s2 中直接压入一个0,这样 -1 - 2就转换为了 0 - 1 - 2。
- 考虑括号里面第一个有效字符是负号的情况,如:1 - ( - 1 - 1),同样的,往s2中直接压入一个0,就将式子转换为了1 - (0 - 1 - 1) 。
例如,将中缀表达式“1+((2+3)×4)-5”转换为后缀表达式的过程如下:
代码如下:
static List<String> infixToSuffix(String s){ Stack<String> s1 = new Stack<>(); List<String> s2 = new ArrayList<>(); int i = 0; while(s.charAt(i) == ' '){ i++; } if(s.charAt(i) == '-'){ s2.add("0"); } String str = ""; char ch = ' '; for(; i < s.length(); i++){ ch = s.charAt(i); if(ch == ' ') continue; //如果是数字 if(ch >= '0' && ch <='9'){ //注意多位数的情况 int j = i + 1; //str赋初值 str = ch + "" ; while(j < s.length() && s.charAt(j) >= '0' && s.charAt(j) <= '9'){ str += s.charAt(j); j++; } s2 .add(str); i = j - 1; } else{ //当ch == ')'时,将s1中的符号不断弹出s2,直到遇到了"(" if(ch == ')'){ while(!s1.isEmpty() && !s1.peek().equals("(")){ s2 .add(s1.pop()) ; } //将"("弹出s1 s1.pop(); } else if(ch == '('){ s1.push(ch+""); if(s.charAt(i+1) == '-'){ s2.add("0"); } } //当ch == '('时直接入s1,当栈顶为'('时,ch直接入s1 else if(s1.isEmpty() || s1.peek().equals("(")) s1.push(ch+""); //当ch优先级大于栈顶符号优先级时,ch直接入栈 else if(priority(ch) > priority(s1.peek().charAt(0))){ s1.push(ch + ""); } //将s1栈顶元素加入s2,直到s1栈顶元素优先级小于ch,再将ch入s1 else{ do{ s2 .add(s1.pop()); }while(!s1.isEmpty() && priority(ch) <= priority(s1.peek().charAt(0))); s1.push(ch + ""); } } } //最后将s1中元素全部加入s2 while(!s1.isEmpty()){ s2.add(s1.pop()); } return s2; }
力扣上相应的练习: