1.Java前缀、中缀及后缀表达式(看符号位置)
1.1.前缀表达式
- 前缀表达式的运算符位于操作符之前2
- 例如:中缀表达式 (3+4)*5-6 对应的前缀表达式为:- * + 3 4 5 6
- 中缀表达式转前缀表达式的步骤: (1) 初始化两个栈:运算符栈S1和储存中间结果的栈S2; (2) 从右至左扫描中缀表达式;
(3) 遇到操作数时,将其压入S2;
(4) 遇到运算符时,比较其与S1栈顶运算符的优先级:
(4-1) 如果S1为空,或栈顶运算符为右括号“)”,则直接将此运算符入栈;
(4-2) 否则,若优先级比栈顶运算符的较高或相等,也将运算符压入S1;
(4-3) 否则,将S1栈顶的运算符弹出并压入到S2中,再次转到(4-1)与S1中新的栈顶运算符相比较;
(5) 遇到括号时:
(5-1) 如果是右括号“)”,则直接压入S1;
(5-2) 如果是左括号“(”,则依次弹出S1栈顶的运算符,并压入S2,直到遇到右括号为止,此时将这一对括号丢弃;
(6) 重复步骤(2)至(5),直到表达式的最左边;
(7) 将S1中剩余的运算符依次弹出并压入S2;
(8) 依次弹出S2中的元素并输出,结果即为中缀表达式对应的前缀表达式。
例如: (3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6 , 针对前缀表达式求值步骤如下:
从右至左扫描,将6、5、4、3压入堆栈
遇到+运算符,因此弹出3和4(3为栈顶元素,4为次顶元素),计算出3+4的值,得7,再将7入栈
接下来是×运算符,因此弹出7和5,计算出7×5=35,将35入栈
最后是-运算符,计算出35-6(先弹出的数—后弹出的数)的值,即29,由此得出最终结果
计算前缀表达式实现代码:
package example.project.expression;
import java.util.Stack;
/**
* @author tian
* @Package PACKAGE_NAME
* @date 2022/7/23 16:23
* @description 前缀表达式实现类
*/
public class PrefixExpression {
public static boolean recognize(char ch) {
return ch == '*' || ch == '+' || ch == '-' || ch == '/';
}
public static int operateNum(int num1, int num2, char ch) {
int res = 0;
switch (ch) {
case '+':
res = num1 + num2;
break;
case '-':
res = num1 - num2;
break;
case '*':
res = num1 * num2;
break;
case '/':
res = num1 / num2;
break;
default:
System.out.println("无法识别符号");
}
return res;
}
public static void main(String[] args) {
//数字栈
Stack<String> valueStack = new Stack<String>();
//输入字符串
// Scanner sc = new Scanner(System.in);
// String s = sc.nextLine();
String s = "- * + 3 4 5 6";
String[] list = s.split(" ");
String str = "";
int length = list.length;
for (int i = length -1; i >= 0; i--) {
str = list[i];
if(str.length() != 1){//这个一定为多位数,直接入栈
valueStack.push(str);
}else {//判断是运算符还是数字
if(recognize(str.charAt(0))){//如果为运算符
int num1 = Integer.parseInt(valueStack.pop());
int num2 = Integer.parseInt(valueStack.pop());
//计算值
valueStack.push(String.valueOf(operateNum(num1, num2, str.charAt(0))));
}else {//如果为数字
valueStack.push(str);
}
}
}
System.out.println("数据栈的个数=" + valueStack.size());
System.out.printf("%s=%s", s, valueStack.pop());
}
}
1.2.中缀表达式
-
中缀表达式为人们常用的数学计算表达式
-
例如 :(3+4)*5-6
-
中缀表达式的求值往往为人们所熟悉,但是对计算机来说不方便操作,若要实现对中缀表达式的求值则需要许多逻辑判断。因此,在计算结果时,往往会将中缀表达式转成其他表达式进行计算(一般转换成后缀表达式)
-
实现代码:
package example.project.expression;
import java.util.Arrays;
import java.util.Stack;
/**
* @author tian
* @Package example.project.expression
* @date 2022/7/23 16:27
* @description 中缀表达式
*/
public class NifixExpression {
//Java使用的就是中缀表达式-" ( 10 + 20 / 2 * 3 ) / 2 + 8"
private static final String nifix="(10+20/2*3)/2+8";
public static void main(String[] args) {
int result = evaluateExpression(nifix);
System.out.println("计算结果为:"+result);
}
/**
* 1.格式化表达式
*
*/
private static String formatExpression(String expression){
//拼接字符串
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < expression.length(); i++) {
//获取字符
char c = expression.charAt(i);
//如果为操作符,则两边添加空格并添加进stringBuilder
if(c == '(' || c == ')' || c == '+' || c == '-' || c == '*' || c == '/'){
stringBuilder.append(' ');
stringBuilder.append(c);
stringBuilder.append(' ');
}else {
//否则添加数加两边空格
stringBuilder.append(c);
}
}
return stringBuilder.toString();
}
/**
* 2.栈实现中缀表达式计算需要建立两个栈;
* 符号栈 + 数字栈
* :
2.1 符号栈如果为空,或者栈顶为左括号,则可以直接进栈
2.2 如果栈顶符号优先级比将要入栈的符号优先级低,则可以
直接进栈
2.3 如果栈顶符号优先级与将要入栈的优先级相等,则先弹出栈顶
,同时弹出数字栈的两个数字使用弹出的符号进行计算,运算结果
再压入数字栈,最后符号入栈
2.4 如果将要入栈的符号为右括号,则弹出栈顶并使用弹出的符号对数字栈
弹出的两个数字进行计算,重复过程直到符号栈栈顶元素为左括号为止
此时为处理完了括号的内容。
2.5 最后对最后一个符号和两个数字进行特殊操作
*/
private static int evaluateExpression(String expression){
/*
stack继承了vecter,自己独有的方法只有下面5个:
1.Object push(Object element):将元素推送到堆栈顶部。
2.Object pop():移除并返回堆栈的顶部元素。如果我们在调用堆栈为空时调用pop(),则抛出’EmptyStackException’异常。
3.Object peek():返回堆栈顶部的元素,但不删除它。
4.boolean empty():如果堆栈顶部没有任何内容,则返回true。否则,返回false。
5.int search(Object element):确定对象是否存在于堆栈中。如果找到该元素,它将从堆栈顶部返回元素的位置。否则,它返回-1。
*/
//符号栈
Stack<Character> operatorStack = new Stack<Character>();
//数字栈
Stack<Integer> numberStack = new Stack<Integer>();
//1.格式化表达式
String formatExpression = formatExpression(expression);
System.out.println(formatExpression);
//切割格式化字符串
String[] strings = formatExpression.split(" ");
System.out.println(Arrays.toString(strings));
//遍历表达式
for (String string : strings) {
//1.如果遇到空字符,则直接跳过
if(string.length() == 0){
continue;
}
//如果字符串长度大于1.必然是数字,比如10、20
//如果字符串长度等于1,一种是符号、一种是数字
//跳过取出的字符串的第一个字符是符号,证明改字符串就是符号
//string.chatAt(0)为符号
//2.string 读入字符串的第一个字符是+或-的情况
else if(string.charAt(0) == '+' || string.charAt(0) == '-'){
//operatorStack.peek()符号栈的栈顶
while(!operatorStack.isEmpty() && (operatorStack.peek() == '+'
|| operatorStack.peek() == '-'
|| operatorStack.peek() == '*'
|| operatorStack.peek() == '/' )){
//计算处理
processAnOperator(operatorStack,numberStack);
}
//栈内元素处理完时,将待进栈元素进栈
operatorStack.push(string.charAt(0));
}
//3.读入字符为 * 或 /时
else if(string.charAt(0) == '*' || string.charAt(0) == '/'){
while(!operatorStack.isEmpty() && (operatorStack.peek() == '*' || operatorStack.peek() == '/' )){
//计算处理
processAnOperator(operatorStack,numberStack);
}
operatorStack.push(string.charAt(0));
}
//4.读入字符为左括号时直接进栈
else if(string.charAt(0) == '('){
operatorStack.push(string.charAt(0));
}
//5.读入字符为右括号时,此时要对左括号和右括号之间的数和符号进行处理
else if(string.charAt(0) == ')'){
//只要符号栈栈顶不是左括号,进行循环处理
while(operatorStack.peek() != '('){
processAnOperator(operatorStack,numberStack);
}
//处理完括号内容时,直接弹出左括号
operatorStack.pop();
}
//6.当读入字符为数字时
else {
numberStack.push(Integer.parseInt(string));
}
}
//最后一步,对最后一组运算进行处理
while (!operatorStack.isEmpty()){
processAnOperator(operatorStack,numberStack);
}
//将压入栈顶元素弹出就是最后的结果
return numberStack.pop();
}
/**
* 计算处理-处理函数
* @param operatorStack 符号栈
* @param numberStack 数字栈
*/
private static void processAnOperator(Stack<Character> operatorStack, Stack<Integer> numberStack) {
//运算数1
int num1 = numberStack.pop();
//运算数2
int num2 = numberStack.pop();
//运算数3
char c = operatorStack.pop();
switch (c){
case '+':{
numberStack.push(num2 + num1);
break;
}
case '-':{
numberStack.push(num2 - num1);
break;
}
case '*':{
numberStack.push(num2 * num1);
break;
}
case '/':{
numberStack.push(num2 / num1);
break;
}
}
}
}
1.3.后缀表达式(逆波兰表达式)
-
与前缀表达式相似,只是运算符位于操作数之后-运算符要算优先级
-
例如: 中缀表达式 (3+4)*5-6 对应的后缀表达式为 3 4 + 5 * 6 -
中缀表达式 后缀表达式 a+b a b + a+(b-c) a b c - + a+(b-c)*d a b c - d * + a+d*(b-c) a d b c - * + a=1+3 a 1 3 + = -
后缀表达式的计算机求值:
从左至右扫描表达式,遇到数字时,将数字压入数栈,遇到运算符时,弹出数栈的栈顶的两个数,并用运算符对它们做相应的运算,将结果入栈。重复上述过程直到表达式扫描完毕,最后栈中的数值即为后缀表达式的运算结果。
实现代码:
package example.project.expression;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/**
* @author tian
* @Package example.project.expression
* @date 2022/7/23 16:26
* @description 后缀表达式
*/
public class SuffixExpression {
public static void main(String[] args) {
//先定义给逆波兰表达式
//(30+4)×5-6 => 30 4 + 5 × 6 - => 164
// 4 * 5 - 8 + 60 + 8 / 2 => 4 5 * 8 - 60 + 8 2 / +
//测试
//说明为了方便,逆波兰表达式 的数字和符号使用空格隔开
//String suffixExpression = "30 4 + 5 * 6 -";
String suffixExpression = "4 5 * 8 - 60 + 8 2 / +"; // 76
//思路
//1. 先将 "3 4 + 5 × 6 - " => 放到ArrayList中
//2. 将 ArrayList 传递给一个方法,遍历 ArrayList 配合栈 完成计算
List<String> list = getListString(suffixExpression);
System.out.println("rpnList=" + list);
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 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,由此得出最终结果
*/
public static int calculate(List<String> ls) {
// 创建栈, 只需要一个栈即可
Stack<String> stack = new Stack<String>();
// 遍历 ls
for (String item : ls) {
// 这里使用正则表达式来取出数
if (item.matches("\\d+")) { // 匹配的是多位数
// 入栈
stack.push(item);
} else {
// pop出两个数,并运算, 再入栈
int num2 = Integer.parseInt(stack.pop());
int num1 = Integer.parseInt(stack.pop());
int res = 0;
if (item.equals("+")) {
res = num1 + num2;
} else if (item.equals("-")) {
res = num1 - num2;
} else if (item.equals("*")) {
res = num1 * num2;
} else if (item.equals("/")) {
res = num1 / num2;
} else {
throw new RuntimeException("运算符有误");
}
//把res 入栈
stack.push("" + res);
}
}
//最后留在stack中的数据是运算结果
return Integer.parseInt(stack.pop());
}
}