我们一般给出的一个表达式是如 String expression = "7*20-1*4*2+3"这样的字符串,但是如果让我们通过程序去计算这样的一个表达式该如何实现呢?
这里的计算器只是为了展现栈这种数据结构的运用,没有涉及小数、括号的运算,有兴趣的码友可以在此基础上进行完善。
这里我们可以借助栈的数据结构来实现计算器,那么如何实现呢?
首先,我们需要先创建两个栈,一个是符号栈,一个是数字栈。符号栈是用来记录运算符,而数字栈是用来记录数字。我们以一个简单表达式 String expression = "7*2+1" 为例:
对于这个字符串,我们可以通过遍历的方式以及通过String的api获得其每一个值
思路如下
根据如图所示思路,我们需要的前提准备有:
1.创建两个栈,一个数字栈,一个符号栈,并编写对应的入栈,出栈操作的方法
2.一个判断是否是运算符的方法
3.一个获得符号栈栈顶符号的方法
4.一个比较运算符优先级的方法
5.一个进行计算的方法
细节点:
1.对于遍历过程中,因为我们是对这个字符串单个遍历的,所以,遍历到多位数时,我们需要把数字拼接起来,这次我采用的是StringBuffer进行数据的拼接
2.对于遍历出的数字,是char类型的数字,所以我们需将其进行转化,这涉及到计算机底层对于整数的存储,我这里就不多加赘述,只需要对遍历出的单个数字-48,就可以获得int类型的数字。
3.我把对符号栈的操作封装成一个方法既为了减少计算器方法的代码冗余也为了方便使用递归,我在对符号栈的操作中采用了一个递归,原因如下,大家可以在代码中仔细了解。
下面实现的代码附上:
package com.liu.stack;
/**
* @author liuweixin
* @create 2021-09-07 22:39
*/
//借助栈实现一个综合计算器,中缀表达式
public class Calculator {
numStack numStack;//数字栈
operStack operStack;//符号栈
/**
* 初始话数字栈和符号栈
*
* @param numLength 数字栈的长度
* @param operLength 符号栈的长度
*/
public Calculator(int numLength, int operLength) {
this.numStack = new numStack(numLength);
this.operStack = new operStack(operLength);
}
public static void main(String[] args) {
String expression = "7*20-1*4*2+3";//135
Calculator calculator = new Calculator(expression.length(), expression.length());
int result = calculator.calculator(expression);
System.out.println(result);
}
/**
* 将传入的中缀表达式进行计算
*
* @param expression
*/
public int calculator(String expression) {
StringBuffer stringBuffer = new StringBuffer();//用于拼接数字(当数字的数量超过两位数时)
for (int i = 0; i < expression.length(); i++) {
char data = expression.charAt(i);//将字符串中的每个数据进行遍历
if (!isOper(data)) {//判断不为符号
if (numStack.isFull()) {
throw new RuntimeException("栈中已满,无法再入栈");
}
int num = (int) data - 48;//char型的数据在底层存储的字符集编码,需要进行转换才能获得int型的数据
stringBuffer.append(num);//储存到stringBuffer中
} else {
//当检测到符号,先把stringBuffer中的数字入数字栈
if (stringBuffer != null) {
//先把stringBuffer中的数据显示,然后转化成Integer数据,再用自动封箱拆箱原理,添加到数字栈中
numStack.push(Integer.parseInt(stringBuffer.toString()));
stringBuffer.delete(0, stringBuffer.length());//重置stringBuffer
}
//处理符号栈的操作
operCal(data);
}
}
//循环结束后,最后遍历的是数字,所以仍要把数字添加到数字栈中
if (stringBuffer != null) {
//先把stringBuffer中的数据显示,然后转化成Integer数据,再用自动封箱拆箱原理,添加到数字栈中
numStack.push(Integer.parseInt(stringBuffer.toString()));
stringBuffer.delete(0, stringBuffer.length());//重置stringBuffer
}
//最后再把数字栈与符号栈的同级的数据pop出来计算
while (numStack.top!=0){//此时数字栈中仍有2个数据
int num1 = numStack.pop();
int num2 = numStack.pop();
char data = operStack.pop();
//此时剩余的为同级的运算符,故不需要考虑优先级
int result = cal(num1, num2, data);
//再把结果入栈数据栈
numStack.push(result);
}
//循环结束,此时数字栈中只剩一个最终结果
return numStack.pop();
}
/**
* 处理检测到符号时的符号栈操作,写成单独一个方式是为了方便递归以及减少calculator()方法的冗余
* @param data 符号
*/
public void operCal(char data) {
if (operStack.isEmpty()) {
//如果符号栈为空,直接入栈
operStack.push(data);
} else {
//判断符号的优先级,与栈顶的符号进行比对
if (priority(data) == priority(operStack.getTop())) {
//符号优先级一致,先计算前一个符号的,新的符号直接入栈
int num1 = numStack.pop();
int num2 = numStack.pop();
char oper = operStack.pop();
int result = cal(num1, num2, oper);
//将计算结果入栈
numStack.push(result);
//把符号入栈
operStack.push(data);
} else if (priority(data) > priority(operStack.getTop())) {
//符号的优先级大于栈顶优先级,直接将符号入栈
operStack.push(data);
} else {
//符号优先级小于栈顶符号的优先级,则需要弹出数据栈顶部的两个数据并弹出符号栈栈顶的数据,进行计算
int num1 = numStack.pop();
int num2 = numStack.pop();
char oper = operStack.pop();
int result = cal(num1, num2, oper);
//将结果入栈
numStack.push(result);
// //符号入栈
// operStack.push(data);
//递归的原因是:有可能该符号的优先级仍小于栈顶的优先级,故递归处理
//如7*20-1*4*2+3,遍历到+时,计算完4*2,此时还要判断该符号跟新的符号栈顶的数据的优先级
//如此递归的话,需把上面符号入栈的操作给删除,因为递归已经执行了该操作
operCal(data);
}
}
}
/**
* 进行计算
*
* @param num1 第一个数字
* @param num2 第二个数字
* @param data 符号
* @return 返回计算结果
*/
public int cal(int num1, int num2, char data) {
switch (data) {
case '*':
return num2 * num1;
case '/':
return num2 / num1;
case '+':
return num2 + num1;
case '-':
return num2 - num1;
}
throw new RuntimeException("传入符号异常");
}
/**
* 判断是否是符号
*
* @param data 传入char
* @return 如果返回true,则是符号,如果返回false,则不是符号
*/
public boolean isOper(char data) {
return data == '*' || data == '/' || data == '+' || data == '-';
}
/**
* 判断传入符号的优先级
*
* @return 传入的符号是乘除,则优先级为2 传入符号是加减,则优先级为1
*/
public int priority(char data) {
if (data == '*' || data == '/') {//传入的符号是乘除,则优先级为2
return 2;
}
if (data == '+' || data == '-') {//传入符号是加减,则优先级为1
return 1;
}
//传入符号既不是乘除,也不是加减,报异常。
throw new RuntimeException("传入的符号异常");
}
}
//创建数字栈
class numStack {
int maxSize;//栈的长度
int[] arr;//使用数组实现栈
int top;//栈顶的指针
public numStack(int maxSize) {
this.maxSize = maxSize;
this.arr = new int[this.maxSize];
top = -1;
}
/**
* 入栈操作
*
* @param num
*/
public void push(int num) {
if (isFull()) {//此时栈已满,无法再加入数据
System.out.println("栈已满,无法加入");
return;
}
arr[++top] = num;//入栈
}
/**
* 出栈操作
*
* @return 返回出栈的数据
*/
public int pop() {
if (isEmpty()) {
throw new RuntimeException("栈空,无法弹栈");
}
return arr[top--];//弹栈
}
/**
* 遍历栈
*/
public void show() {
if (isEmpty()) {
System.out.println("栈空");
return;
}
while (top != -1) {
System.out.println(arr[top--]);
}
}
/**
* 判断栈中是否为满
*
* @return 返回true,则满。反之
*/
public boolean isFull() {
return top == arr.length - 1;
}
/**
* 判断栈中是否为空
*
* @return 返回true,则空 反之
*/
public boolean isEmpty() {
return top == -1;
}
}
//创建符号栈
class operStack {
int maxSize;//栈的长度
char[] arr;//使用数组实现栈
int top;//栈顶的指针
public operStack(int maxSize) {
this.maxSize = maxSize;
this.arr = new char[this.maxSize];
top = -1;
}
/**
* 入栈操作
*
* @param oper
*/
public void push(char oper) {
if (isFull()) {//此时栈已满,无法再加入数据
System.out.println("栈已满,无法加入");
return;
}
arr[++top] = oper;//入栈
}
/**
* 出栈操作
*
* @return 返回出栈的数据
*/
public char pop() {
if (isEmpty()) {
throw new RuntimeException("栈空,无法弹栈");
}
return arr[top--];//弹栈
}
/**
* 判断栈中是否为满
*
* @return 返回true,则满。反之
*/
public boolean isFull() {
return top == arr.length - 1;
}
/**
* 判断栈中是否为空
*
* @return 返回true,则空 反之
*/
public boolean isEmpty() {
return top == -1;
}
/**
* 获取栈顶的符号
*
* @return
*/
public char getTop() {
if (isEmpty()) {
throw new RuntimeException("栈中没有数据");
}
return arr[top];
}
}