请输入一个表达式
计算式:[722-5+1-5+3-3] 点击计算【如下图】
请问: 计算机底层是如何运算得到结果的? 注意不是简单的把算式列出运算,因为我们看这个算式 7 * 2 * 2 - 5, 但是计算机怎么理解这个算式的(对计算机而言,它接收到的就是一个字符串),我们讨论的是这个问题-> 栈。
import java.util.Scanner;
public class ArrayStackDemo {
public static void main(String[] args) {
//测试
//先创建一个ArrayStack对象
ArrayStack stack = new ArrayStack(4);
String key = "";
boolean loop = true;//控制是否退出菜单
Scanner scanner = new Scanner(System.in);
while(loop) {
System.out.println("show:显示栈");
System.out.println("exit:退出程序");
System.out.println("push:表示添加数据到栈");
System.out.println("pop:从栈取出数据");
System.out.println("请输入你的选择:");
key=scanner.next();
switch(key) {
case "show":
stack.list();
break;
case "push":
System.out.println("请输入一个数:");
int value = scanner.nextInt();
stack.push(value);
break;
case "pop":
try {
System.out.println("出栈的数据为:"+stack.pop());
} catch (Exception e) {
System.out.println(e.getMessage());
}
break;
case "exit":
scanner.close();
loop = false;
break;
}
}
System.out.println("程序退出");
}
}
//定义一个ArrayStack表示栈
class ArrayStack{
private int maxSize;//栈的大小
private int[] stack;//用数组模拟栈
private int top=-1;//表示栈顶
//构造函数
public ArrayStack(int maxSize) {
this.maxSize=maxSize;
stack = new int[this.maxSize];
}
//栈满
public boolean isFull() {
return top == maxSize-1;
}
//栈空
public boolean isEmpty() {
return top==-1;
}
//入栈
public void push(int value) {
//先判断是否满
if(isFull()) {
System.out.println("栈满");
return;
}
top++;
stack[top]=value;
}
//出栈
public int pop() {
//先判断是否为空
if(isEmpty()) {
//抛出异常
throw new RuntimeException("栈空,没有数据");
}
return stack[top--];
}
//遍历栈,从栈顶开始显示数据
public void list() {
//判断是否为空
if(isEmpty()) {
System.out.println("栈空,没有数据");
return;
}
//从栈顶显示
for(int i=top;i>=0;i--) {
System.out.printf("stack[%d]=%d\n",i,stack[i]);
}
}
}
编写一个计算器(支持正负、加减乘除运算,不支持括号)
public class Calculator {
public static void main(String[] args) {
// 测试
String expression = "-10-6/3*2+5*2/2";//中缀表达式
// 创建数栈和符号栈
ArrayStack2 numStack = new ArrayStack2(10);
ArrayStack2 operStack = new ArrayStack2(10);
// 定义扫描
int index = 0;
int flag = 0;// 判断是否为负数
int num1 = 0;
int num2 = 0;
int oper = 0;
int res = 0;
char ch = ' ';// 将每次扫描得到的char保存如ch
String keepNum = "";// 用于拼接多位数
// 开始扫描
while (true) {
// 依次得到每一个字符
ch = expression.charAt(index);
// 判断ch是什么,然后再做相应处理
if (operStack.isOper(ch)) {// 如果是运算符
// 判断是否为-号
if (ch == '-') {
// 判断是否在算式首位
if (index == 0) {
flag = 1;
index++;
continue;
} else {
flag = 1;
ch = '+';
}
}
// 判断当前符号栈是否为空
if (!operStack.isEmpty()) {
// 如果符号栈有操作符,就进行比较,如果当前的操作符的优先级小于或者等于栈中的操作符, 就需要从数栈中pop出两个数,
// 在从符号栈中pop出一个符号,进行运算,将得到结果,入数栈,然后将当前的操作符入符号栈
if (operStack.priority(ch) <= operStack.priority(operStack.peek())) {
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
res = numStack.cal(num1, num2, oper);
// 把运算结果如数栈
numStack.push(res);
// 把当前操作符入符号栈
operStack.push(ch);
} else {
operStack.push(ch);
}
} else {// 如果为空则直接入栈
operStack.push(ch);
}
} else {// 如果是数,则直接入数栈
// 如果当前操作符优先级大于栈顶的操作符,则直接如栈
// numStack.push(ch - '0');不能发现一个数就立即入栈,因为可能是多位数
// 故需要再往后看一位,如果是数则继续扫描,如果是符号则可以如栈
// 因此需要定义一个字符串变量用于拼接
keepNum += ch;
// 如果ch已经是expression的最后一位,就直接入栈
if (index == expression.length() - 1) {
if (flag == 1) {
numStack.push(-Integer.parseInt(keepNum));
} else {
numStack.push(Integer.parseInt(keepNum));
}
} else {
// 判断下一个是不是数字,如果是数字,则继续扫描;如果是运算符,则入栈
// 不能用index++
if (operStack.isOper(expression.charAt(index + 1))) {
// 入栈
if (flag == 1) {
numStack.push(-Integer.parseInt(keepNum));
} else {
numStack.push(Integer.parseInt(keepNum));
}
// 注意:一定要清空字符串
keepNum = "";
flag = 0;
}
}
}
// 让index加一,判断是否扫描到最后
index++;
if (index >= expression.length()) {
break;
}
}
// 当扫描完毕,则顺序从数栈和符号栈中pop出数和符号,并计算
while (true) {
// 当符号栈为空,则计算到最后的结果
if (operStack.isEmpty()) {
break;
}
num1 = numStack.pop();
num2 = numStack.pop();
oper = operStack.pop();
res = numStack.cal(num1, num2, oper);
numStack.push(res);
}
// 将数栈的最后数pop出来
System.out.printf("表达式%s = %d", expression, numStack.pop());
}
}
// 定义一个ArrayStack2表示栈
class ArrayStack2 {
private int maxSize;// 栈的大小
private int[] stack;// 用数组模拟栈
private int top = -1;// 表示栈顶
// 构造函数
public ArrayStack2(int maxSize) {
this.maxSize = maxSize;
stack = new int[this.maxSize];
}
// 增加一个方法,返回栈顶元素,但不是真正的pop
public int peek() {
return stack[top];
}
// 栈满
public boolean isFull() {
return top == maxSize - 1;
}
// 栈空
public boolean isEmpty() {
return top == -1;
}
// 入栈
public void push(int value) {
// 先判断是否满
if (isFull()) {
System.out.println("栈满");
return;
}
top++;
stack[top] = value;
}
// 出栈
public int pop() {
// 先判断是否为空
if (isEmpty()) {
// 抛出异常
throw new RuntimeException("栈空,没有数据");
}
return stack[top--];
}
// 遍历栈,从栈顶开始显示数据
public void list() {
// 判断是否为空
if (isEmpty()) {
System.out.println("栈空,没有数据");
return;
}
// 从栈顶显示
for (int i = top; i >= 0; i--) {
System.out.printf("stack[%d]=%d\n", i, stack[i]);
}
}
// 返回运算符优先级,用数字表示
// 数字越大,优先级越高
public int priority(int i) {
if (i == '*' || i == '/') {
return 1;
} else if (i == '+' || i == '-') {
return 0;
} else {
return -1;
}
}
// 判断是不是一个运算符
public boolean isOper(int val) {
return val == '+' || val == '-' || val == '*' || val == '/';
}
// 计算方法
public int cal(int num1, int num2, int oper) {
int res = 0;// 用于存放计算结果
switch (oper) {
case '+':
res = num1 + num2;
break;
case '-':
res = num2 - num1;// 注意顺序
break;
case '*':
res = num1 * num2;
break;
case '/':
res = num2 / num1;
break;
default:
break;
}
return res;
}
}
前缀表达式(波兰表达式):
前缀表达式的计算机求值:
从右至左扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素 和 次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果。
例如:(3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6 , 针对前缀表达式求值步骤如下:
中缀表达式:
后缀表达式(逆波兰表达式):
后缀表达式的计算机求值:
从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 和 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果。
例如: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 - , 针对后缀表达式求值步骤如下:
下面我们完成一个逆波兰计算器:(已经将算式转换为了逆波兰表达式)
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class PolandNotation {
public static void main(String[] args) {
//先定义一个逆波兰表达式
//(30+4)*5-6 => 30 4 + 5 * 6 -
//为了方便,数字和符号间用空格隔开
String suffixExpression = "30 4 + 5 * 6 -";
//思路:
//1.先将3 4 + 5 * 6 -放到ArrayList中
//2.将ArrayList传递给一个方法,遍历ArrayList配合栈完成计算
List<String> list = getListString(suffixExpression);
int res = calculate(list);
System.out.println("计算的结果是:"+res);
}
//将逆波兰表达式,依次将数据放入ArrayList中
public static List<String> getListString(String suffixExpression){
//将suffixExpression分割
String[] split = suffixExpression.split(" ");
List<String> list = new ArrayList<String>();
for(String str:split) {
list.add(str);
}
return list;
}
//完成对逆波兰表达式的计算
public static int calculate(List<String> list) {
//创建一个栈
Stack<String> stack = new Stack<String>();
//遍历
for(String item:list) {
//这里使用正则表达式取出数
if(item.matches("\\d+")) {//匹配的是多位数
//入栈
stack.push(item);
}else {
//pop出两个数并计算,再入栈
int num2=Integer.parseInt(stack.pop());
int num1=Integer.parseInt(stack.pop());
int res=0;
switch(item) {
case "+":
res=num1+num2;
break;
case "-":
res=num1-num2;
break;
case "*":
res=num1*num2;
break;
case "/":
res=num1/num2;
break;
default:
throw new RuntimeException("运算符有误");
}
//把res入栈
stack.push(""+res);//把整数转换为字符串
}
}
//最终留在stack中的数据即为运算结果
return Integer.parseInt(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中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式。
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class PolandNotation {
public static void main(String[] args) {
// 完成将中缀表达式转后缀表达式
// 1. 1+((2+3)*4)-5 => 1 2 3 + 4 * + 5 –
// 2. 因为对字符串操作不方便,因此可以先将1+((2+3)*4)-5的中缀表达式转为ArrayList
// 3.将中缀表达式对应的List转为后缀表达式对应的List
String expression = "1+((2+3)*4)-5";
System.out.println("原算式为:"+expression);
List<String> infixExpression = toInfixExpressionList(expression);
System.out.println("中缀表达式" + infixExpression);
List<String> suffixExpression = parseSuffixExpressionList(infixExpression);
System.out.println("后缀表达式" + suffixExpression);
System.out.println("最终结果为:" + calculate(suffixExpression));
}
// 将中缀表达式转换为List
public static List<String> toInfixExpressionList(String s) {
// 定义一个List存放中缀表达式内容
List<String> list = new ArrayList<String>();
int index = 0;// 用来扫描字符串
String str;// 用于拼接多位数
char c;// 每遍历一个字符存入c中
do {
// 如果是一个非数字,则加入list中
if ((c = s.charAt(index)) < '0' || (c = s.charAt(index)) > '9') {
list.add("" + c);// 将字符转换为字符串
index++;// 后移
} else {// 如果是一个数,则要考虑多位数
str = "";// 先将str清空
while (index < s.length() && (c = s.charAt(index)) >= '0' && (c = s.charAt(index)) <= '9') {
str += c;// 进行拼接
index++;
}
list.add(str);
}
} while (index < s.length());
return list;
}
//将List转换为逆波兰表达式
// [1,+,(,(,2,+,3,),*,4,),-,5] -> [1,2,3,+,4,*,+,5,–]
public static List<String> parseSuffixExpressionList(List<String> list) {
// 定义两个栈
Stack<String> s1 = new Stack<String>();
// 说明:因为s2栈在整个过程没有pop操作,而且最后需要逆序输出,太麻烦,所以可以直接用List替代s2栈
List<String> s2 = new ArrayList<String>();
// 遍历list
for (String item : list) {
if (item.matches("\\d+")) {// 如果是一个数,则直接加入s2中
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 {
// 当item的优先级小于等于s1栈顶的优先级,将s1栈顶的运算符弹出并加入到s2中,再将item与s1中新的栈顶运算符相比较
// 添加一个比较优先级高低的方法
while (s1.size() != 0 && getValue(item) <= getValue(s1.peek())) {
s2.add(s1.pop());
}
// 还需要将item压入栈中
s1.push(item);
}
}
// 将s1中剩余的元素符加入s2中
while (s1.size() != 0) {
s2.add(s1.pop());
}
return s2;
}
// 完成对逆波兰表达式的计算
public static int calculate(List<String> list) {
// 创建一个栈
Stack<String> stack = new Stack<String>();
// 遍历
for (String item : list) {
// 这里使用正则表达式取出数
if (item.matches("\\d+")) {// 匹配的是多位数
// 入栈
stack.push(item);
} else {
// pop出两个数并计算,再入栈
int num2 = Integer.parseInt(stack.pop());
int num1 = Integer.parseInt(stack.pop());
int res = 0;
switch (item) {
case "+":
res = num1 + num2;
break;
case "-":
res = num1 - num2;
break;
case "*":
res = num1 * num2;
break;
case "/":
res = num1 / num2;
break;
default:
throw new RuntimeException("运算符有误");
}
// 把res入栈
stack.push("" + res);// 把整数转换为字符串
}
}
// 最终留在stack中的数据即为运算结果
return Integer.parseInt(stack.pop());
}
//一个方法,用于返回优先级
public static int getValue(String operator) {
switch (operator) {
case "+":
return 1;
case "-":
return 1;
case "*":
return 2;
case "/":
return 2;
case "(":
return 0;
default:
System.out.println("不存在该运算符");
break;
}
return 0;
}
}
注: 仅支持加减乘除、括号的整数运算