中缀表达式:我们日常生活中用的表达式就是中缀表达式,比如4+5*6-7,它的特点就是人容易理解和计算,但是计算机不买账,对于中缀表达式,计算机实现起来复杂度很高,这里模拟了计算机计算中缀表达式过程,实现了一个简易计算器。
几个小概念:
前缀表达式:前缀表达式也就是波兰表达式,由相应的语法树前序遍历得到的结果。
中缀表达式:我们最熟悉的一种表达式,先括号,然后乘除,最后加减。它是由相应语法树中序遍历得到结果。
后缀表达式:后缀表达式就是逆波兰表达式,所有操作符都置于操作数的后面,由相应语法树后序遍历得到。
对于中缀表达式,实现计算需要借助两个栈,而对于前缀和后缀表达式,则只需要一个栈来实现,但是前提是必须将中缀表达式转为前缀或者后缀表达式。
1. 栈结构【数组模拟栈】
package datastructure;
/**
* @author calarqiang
* @create 2022-04-24-10:12
* 使用数组模拟栈,然后实现最简单(十位数)的中缀表达式计算
*/
public class SimpleCalc {
private int MaxSize;
private int[] stack;
private int top = -1; //定义栈顶指针,并初始化为-1
// 初始化栈结构
public SimpleCalc(int maxSize) {
this.MaxSize = maxSize;
this.stack = new int[this.MaxSize];
}
// 判断栈空
public boolean isEmpty() {
return this.top == -1;
}
// 判断栈满
public boolean isFull() {
return this.top == this.MaxSize - 1;
}
// 入栈操作
public void push(int num) {
// 先判栈是否满?
if (isFull()) {
throw new RuntimeException("栈已满!");
}
this.stack[++top] = num;
}
// 出栈操作
public int pop() {
// 判断栈空
if (isEmpty()) {
throw new RuntimeException("栈空!");
}
int elem = this.stack[top--];
return elem;
}
// 查看栈顶元素
public int peek() {
return this.stack[this.top];
}
// 遍历栈
public void print() {
// 判断栈空
if (isEmpty()) {
throw new RuntimeException("栈空,没有元素");
}
for (int i = this.top; i > -1; i--) {
System.out.print("stack[" + i + "]=" + this.stack[i] + "\t");
}
}
// 判断运算符优先级,用数字表示
public static int priority(int oper) {
if (oper == '*' || oper == '/' || oper == '%') {
return 1;
} else if (oper == '+' || oper == '-') {
return 0;
} else {
return -1;
}
}
// 计算表达式的值
public static int calc(int num1, int num2, char oper) {
int res = 0;
switch (oper) {
case '+':
res = num2 + num1;
break;
case '-':
res = num2 - num1;
break;
case '*':
res = num2 * num1;
break;
case '/':
res = num2 / num1;
break;
case '%':
res = num2 % num1;
break;
}
return res;
}
}
2. 中缀表达式计算器实现【借助两个栈实现】
-
初始化一个数栈【用于存储数字】,初始化一个符号栈【用于存储操作符和括号】
-
从左往右扫描表达式,取出每一个字符,然后开始判断是数字类型还是符号类型。
-
数栈
入栈规则:
如果是数字,理论上直接入栈,但是考虑第一个元素是负数和数字多位数情况,这里加入了一个判断语句和循环语句进行处理。 -
符号栈
入栈规则:
~ 为空:无论操作符是左括号还是什么操作符,都直接入栈
~非空:比较符号优先级- 左括号直接入栈
- 右括号时,则弹出操作符栈栈顶操作符,同时弹出数栈中两个元素,进行运算并压入数栈中,然后弹出左括号,右括号也不要入栈,直接匹配消失。
- 入栈操作符优先级大于栈顶操作符优先级,直接入栈
- 入栈操作符优先级小于等于栈顶操作符优先级,从数栈弹出两个元素和栈顶操作符计算,结果入数栈,然后将操作符入符号栈。
注意:运算时,用后弹出的数【栈顶下一个元素】操作前面弹出的数【栈顶元素】
package test;
import datastructure.SimpleCalc;
/**
* @author calarqiang
* @create 2022-04-24-10:20
* 测试简易计算器
*/
public class SimpleCalcTest {
public static void main(String[] args) {
// 定义一个数栈和一个操作符栈
SimpleCalc num = new SimpleCalc(10);
SimpleCalc oper = new SimpleCalc(10);
// 定义要计算的表达式, 有一个更简单的方法,根本就不需要这么复杂,直接将表达式以空格隔开,然后分割存入数组中,再遍历
String str = "-5+12-2";
// 存放获取表达式的单个值
char elem = ' ';
// 存放数栈栈顶弹出的第一个元素
int num1;
// 存放符号栈栈顶弹出的第二个元素
int num2;
// 存放符号栈弹出的符号
char ch;
// 存放最后运算结果
int res = 0;
for (int index = 0; index < str.length(); index++) {
// 定义一个字符串来存储数字
String strNum = "";
elem = str.charAt(index);
// 解决第一个数是负数的情况
if (index == 0 && SimpleCalc.priority(str.charAt(index)) == 0) {
strNum += elem;
index++;
elem = str.charAt(index);
}
// 判断第一个数或者说如果第一个数是负数,就判断第二个数
if (isNum(elem)) {
strNum += elem;
// 如果是数字,先判断后面有几个数字,这样可以解决多位数的情况
while (index != str.length() - 1) {
if (isNum(str.charAt(index + 1))) {
strNum += str.charAt(++index);
} else {
break;
}
}
num.push(Integer.parseInt(strNum));
// num.push(elem-48);
} else if (isLeftBracket(elem)) {
// 如果是左括号,直接入栈
oper.push(elem);
} else if (isRightBracket(elem)) {
// 如果是右括号,则弹出符号栈符号,与数栈运算,直到遇到左括号
ch = (char) oper.pop();
num1 = num.pop();
num2 = num.pop();
// 将计算结果入数栈
num.push(SimpleCalc.calc(num1, num2, ch));
// 弹出左括号,与右括号抵消
oper.pop();
} else {
// 这里表示入栈的是符号
// 如果符号为空,就直接入栈
if (oper.isEmpty()) {
oper.push(elem);
} else {
// 如果符号栈不空,且入栈符号优先级比栈顶符号优先级大,则直接入栈
if (SimpleCalc.priority(elem) > SimpleCalc.priority(oper.peek())) {
oper.push(elem);
} else {
// 如果符号栈不空,但入栈符号优先级比栈顶符号优先级小或者相等,则需要进行运算
num1 = num.pop();
num2 = num.pop();
ch = (char) oper.pop();
num.push(SimpleCalc.calc(num1, num2, ch));
// 然后将符号入栈
oper.push(elem);
}
}
}
}
// 操作完成后,判断符号栈是否为空,如果为空,就证明此时数字栈中只存在一个结果,如果不空,就要进行运算
while (!oper.isEmpty()) {
// 开始运算
num1 = num.pop();
num2 = num.pop();
ch = (char) oper.pop();
num.push(SimpleCalc.calc(num1, num2, ch));
}
// 这里就可以输出数字栈的最后结果了
res = num.pop();
System.out.println("最后结果是:" + res);
}
public static boolean isNum(char ch) {
return (ch >= '0' && ch <= '9') ? true : false;
}
public static boolean isLeftBracket(char ch) {
return ch == '(';
}
public static boolean isRightBracket(char ch) {
return ch == ')';
}
}