大家好,我是皮皮猫吖!
每文一言:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力,却得不到结果的日子,我们把它叫做扎根。
本篇文章:
主要是关于java数据结构与算法的一些应用:中缀表达式与后缀表达式之间的相互转换、带小括号四则表达式的计算。
正文如下:
一、前缀、中缀、后缀表达式(逆波兰表达式)
1)前缀表达式(波兰表达式)
① 前缀表达式又称波兰式,前缀表达式的运算符位于操作数之前。
② 举例说明:
(3+4)×5-6 对应的前缀表达式就是 - × + 3 4 5 6
③ 前缀表达式的计算机求值过程:
前缀表达式从右至左扫描表达式,当遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(栈顶元素和次顶元素),并将结果入栈;重复上述过程直到表达式最左端,最后运算得出的值即为表达式的结果。
④ 例如:(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,由此得出最终结果
2)中缀表达式
① 中缀表达式就是常见的运算表达式,如(3+4)×5-6
② 中缀表达式的求值是我们人最熟悉的,但是对计算机来说却不好操作(前面我们实现的应用【简单四则运算】就能看的这个问题),因此,在计算结果时,往往会将中缀表达式转成其它表达式来操作(一般转成后缀表达式.)
3)后缀表达式(逆波兰表达式)
① 后缀表达式又称逆波兰表达式,与前缀表达式相似,只是运算符位于操作数之后
② 举例:(3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 –
③ 后缀表达式的计算机求值过程:
从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素和 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果。
④ 例子: (3+4)×5-6 对应的后缀表达式就是 3 4 + 5 × 6 -,针对后缀表达式求值步骤如下:
- 从左至右扫描,将3和4压入堆栈;
- 遇到 + 运算符,因此弹出4和3(4为栈顶元素,3为次顶元素),计算出3+4的值,得7,再将7入栈;
- 将5入栈;
- 接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
- 将6入栈;
- 最后是 - 运算符,计算出35-6的值,即29,由此得出最终结果
4)实现一个逆波兰计算器:
① 使用后缀表达式进行运算
package com.data.structure.study4.stack;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;
/**
* 后缀表达式计算
* @author imppm
* @create 2021-03-11-20:06
*/
public class Stack4ReversePoland2 {
public static void main(String[] args) {
//定义逆波兰表达式
String str = "3 4 + 5 * 6 - ";
//获取到逆波兰表达式中字符的集合
List<String> list = getListString(str);
//获取到逆波兰表达式的值
int compute = getCompute(list);
System.out.println(str + " = "+compute);
}
//判断当前字符串是否为数字字符串
private static boolean isDigit(String str){
return str.charAt(0) >= '0' && str.charAt(0) <= '9';
}
//使用运算符进行计算
private static int compute(int a, int b, String str){
switch (str){
case "+":
return a + b;
case "-":
return a - b;
case "*":
return a * b;
case "/":
return a / b;
default:
throw new RuntimeException("无法识别该运算符!");
}
}
//获取到逆波兰表达式计算的值
private static int getCompute(List<String> list){
//定义迭代器:遍历list集合【逆波兰表达式中字符的集合】
Iterator iterator = list.iterator();
//创建一个栈
Stack<Integer> stack = new Stack<>();
//遍历栈
while (iterator.hasNext()){
//依次获取集合中存储的字符串
String str = iterator.next().toString();
//判断该字符串是否为数字字符串
if(isDigit(str)){
//向栈中添加数字
stack.add(str.charAt(0) - '0');
}else{
//如果集合中存储的该字符为运算字符
//两个数据出栈
//进行该运算符运算
int num2 = stack.pop();
int num1 = stack.pop();
int num = compute(num1, num2, str);
//将计算得到的结果入栈
stack.push(num);
}
}
//最后将栈最后的结果返回
return stack.pop();
}
//根据后缀表达式字符串,转换为存储后缀字符的集合
private static List<String> getListString(String str){
//截取获取字符串
String[] ss = str.split(" ");
//创建一个lis t集合
List<String> list = new ArrayList<>();
//遍历字符串
for (String s:ss){
//将遍历的字符串,添加到集合中
list.add(s);
}
//返回集合
return list;
}
}
二、中缀表达式转换为后缀表达式的步骤
1)中缀表达式转后缀表达式的过程,分析如下:
(1) 初始化两个栈:运算符栈s1和储存中间结果的栈s2;
(2) 从左至右扫描中缀表达式;
(3) 遇到操作数时,将其压s2;
(4) 遇到运算符时,比较其与s1栈顶运算符的优先级:
- 1)如果s1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
- 2)否则,若优先级比栈顶运算符的高,也将运算符压入s1;
- 3)否则,将s1栈顶的运算符弹出并压入到s2中,再次转到(4-1)与s1中新的栈顶运算符相比较
(5) 遇到括号时:
- 1)如果是左括号“(”,则直接压入s1
- 2)如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃
(6) 重复步骤2至5,直到表达式的最右边
(7) 将s1中剩余的运算符依次弹出并压入s2
(8) 依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式
2)中缀表达式转后缀表达式的模板代码:
//中缀表达式转换为后缀表达式:传入一个中缀表达式的集合,返回一个后缀表达式的集合
public static List<String> turnInfixToPostfix(List<String> list){
//创建一个后缀表达式的集合:用来存储得到的后缀表达式
List<String> postfixList = new ArrayList<>();
//运算符栈
Stack<String> operatorStack = new Stack<>();
//遍历中缀表达式的集合
for (String ss : list){
//是数字串:直接添加到后缀表达式集合中
if(ss.matches("\\d+")){
postfixList.add(ss+"");
}
//如果直接是左括号,直接压入到运算符栈中
else if("(".equals(ss)){
operatorStack.push(ss);
}
//如果是右括号,进行计算,直到遇到小括号
else if(")".equals(ss)){
//如果是右括号,遍历运算符栈,直到遇到左括号
while (!"(".equals(operatorStack.peek())){
postfixList.add(operatorStack.pop());
}
//将最左边的左括号进行出栈
operatorStack.pop();
}else{
//左括号的运算符,优先级为0,为最小值
//如果运算符栈栈顶优先级大于当前运算符优先级,栈顶运算符添加到后缀表达式集合中
while (operatorStack.size()!=0 && operator.getOperatorPriority(operatorStack.peek()) >= operator.getOperatorPriority(ss)){
postfixList.add(operatorStack.pop());
}
//将当前运算字符串压入到栈中
operatorStack.push(ss);
}
}
//遍历运算符栈剩余的运算符
while (operatorStack.size()!=0){
postfixList.add(operatorStack.pop());
}
//返回后缀表达式集合
return postfixList;
}
class operator{
//加法运算符优先级
private static final int SUM_PRIORITY = 1;
//减法运算符优先级
private static final int SUB_PRIORITY = 2;
//乘法运算符优先级
private static final int MUL_PRIORITY = 2;
//除法运算符优先级
private static final int DIV_PRIORITY = 2;
//获取运算符的优先级
public static int getOperatorPriority(String data){
int result = 0;
switch (data){
case "+":
result = SUM_PRIORITY;
break;
case "-":
result = SUB_PRIORITY;
break;
case "*":
result = MUL_PRIORITY;
break;
case "/":
result = DIV_PRIORITY;
break;
default:
break;
}
return result;
}
}
三、应用:带小括号的四则表达式运算(中缀转后缀表达式方法)【注:这个博客(栈实现带符号的四则表达式计算)有其他方法】
1)方法一:使用中缀表达式转后缀表达式的方法【略微复杂】:
package com.data.structure.study4.stack;
import java.util.Iterator;
import java.util.Stack;
/**
* @author imppm
* @create 2021-03-11-23:10
*/
public class Stack5InfixPostfix1 {
public static void main(String[] args) {
Stack5 stack5 = new Stack5();
//stack4.compute("7*2*2-5+1-5+3-4");//18
//stack4.compute("7*2*2-2*1-5*3-4");//5
//stack4.compute("100-(2-3)*2");//104
// Stack<String> compute = stack5.compute("(1+2)-3*4+2*(1+2)");//3-12+6=-3 // 1 2 + 3 4 * - 2 1 2 + * +
// Stack<String> compute = stack5.compute("1+((2+3)*4)-5");
// Stack<String> compute = stack5.compute("7*2*2-5*(2-1)*3-4");//28-5-15-4+1=5 28-5-5-3-4+1
Stack<String> compute = stack5.compute("123+((2+3)*4)-5");//28-5-15-4+1=5 28-5-5-3-4+1
//获取stack的iterator
Iterator it = compute.iterator();
//遍历栈:可以从前往后遍历
while (it.hasNext()){
System.out.print(it.next()+" ");
}
//使用后续存储的栈,得到最后计算的结果
String s = stack5.postfixCompute(compute);
System.out.println(" = "+s);
}
}
class Stack5 {
//加法运算符的优先级
private final int sumPriority = 0;
//减法运算符的优先级
private final int subPriority = 0;
//乘法运算符的优先级
private final int mulPriority = 1;
//除法运算符的优先级
private final int divPriority = 1;
//左括号的优先级
private final int leftParenthesisPriority = 2;
//右括号的优先级
private final int rightParenthesisPriority = -1;
//运算符栈
private char[] operatorStack = new char[10];
//运算符栈栈顶指针
private int operatorTop = -1;
//用来判断是否输入的式多位数
private boolean flag = false;
//中缀表达式转换为后缀表达式
public Stack<String> compute(String str){
//初始化一个栈,用来存储后缀表达式
Stack<String> stack = new Stack<>();
//遍历当前字符
for (int i = 0; i < str.length(); i++){
//获取字符
char ch = str.charAt(i);
//是否为数字
if(isDigit(ch)){
if(flag){
//多位数字判断
String data = stack.pop();
int int_data = Integer.parseInt(data);
int_data = int_data * 10 + (ch - '0');
stack.push(int_data+"");
}else{
//数字直接入栈
stack.push((ch-'0')+"");
}
//当前数字连续
flag = true;
}
//是否为运算符
else{
//当前数字不连续
flag = false;
//判断运算符栈是否为空:如果运算符栈中没有运算符,直接将该运算符压入栈中
if(operatorTop==-1){
//将该运算符压入栈中
operatorStack[++operatorTop] = ch;
continue;
}
//获取运算符栈顶元素
char operatorStackTopData = getOperatorStackTopData();
//如果运算符栈顶元素优先级小于当前运算符优先级,直接入栈
if(readOperatorPriority(operatorStackTopData) < readOperatorPriority(ch)){
operatorStack[++operatorTop] = ch;
}
//栈顶运算符的优先级大于或等于当前运算符的优先级
else{
while(true) {
//如果栈顶运算符的优先级小于当前运算符的优先级,打破该循环
if(readOperatorPriority(getOperatorStackTopData()) < readOperatorPriority(ch)){
operatorStack[++operatorTop] = ch;
break;
}
//如果当前栈顶元素为'(',后面的运算符不为')'
if (getOperatorStackTopData() == '(' && ch != ')') {
//将ch元素,压入栈中
operatorStack[++operatorTop] = ch;
break;
}
//如果当前栈顶元素为'(',后面的运算符为')',栈顶指针-1,')'不入栈
else if (getOperatorStackTopData() == '(') {
operatorTop--;
break;
}
//获取到运算符
char operator = operatorStack[operatorTop--];
//将运算符压入到后缀表达式栈中
stack.push(operator+"");
//如果运算符栈为空:将当前运算符压入到栈中
if(operatorTop==-1){
operatorStack[++operatorTop] = ch;
break;
}
}
}
}
}
while (operatorTop!=-1){
//获取到运算符
char operator = operatorStack[operatorTop--];
//将运算符压入到后缀表达式栈中
stack.push(operator + "");
}
return stack;
}
//是否为数字
private boolean isDigit(char data){
return data >= '0' && data <= '9';
}
//获取运算符栈栈顶元素
private char getOperatorStackTopData() {
return operatorStack[operatorTop];
}
//读取当前运算符的优先级
private int readOperatorPriority(char data) {
switch (data) {
case '+':
return sumPriority;
case '-':
return subPriority;
case '*':
return mulPriority;
case '/':
return divPriority;
case '(':
return leftParenthesisPriority;
case ')':
return rightParenthesisPriority;
default:
throw new RuntimeException("运算符输入有误!");
}
}
//两个数值进行计算
private static int compute(int a, int b, String str){
switch (str){
case "+":
return a + b;
case "-":
return a - b;
case "*":
return a * b;
case "/":
return a / b;
default:
throw new RuntimeException("无法识别该运算符!");
}
}
//后缀表达式的计算,传入的是后缀表达式栈
public String postfixCompute(Stack stack){
//获取集合栈的迭代器
Iterator it = stack.iterator();
//存储数据的栈:用来存储数,
Stack<String> stack1 = new Stack<>();
//遍历存储数据的栈
while (it.hasNext()){
//拿到栈中的数据
String data = it.next().toString();
//该数据是否为数字
if(data.matches("\\d+")){
//如果是数字串存储到新的栈中
stack1.push(data);
}else{
//如果是运算符,进行计算
//获取数据1
int num2 = Integer.parseInt(stack1.pop());
//获取数据2
int num1 = Integer.parseInt(stack1.pop());
//使用运算符进行计算
int num = compute(num1, num2, data);
//将运算得到的结果压入栈中
stack1.push(num+"");
}
}
//将最后的结果压入栈中
return stack1.pop();
}
}
2)方法二:使用中缀表达式转后缀表达式的方法【较简单】:
package com.data.structure.study4.stack;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
/**
*
* 中缀字符串进行四则运算:
* 1.先将中缀表达式转换成后缀表达式
* 2.计算后缀表达式
* @author imppm
* @create 2021-03-12-18:02
*/
public class Stack5InfixPostfix2 {
public static void main(String[] args) {
//中缀表达式
//注意表达式中的*号,就是乘号
String expression = "123+((2+3)*4)-5";
//中缀字符串转换为中缀集合:方便遍历
List<String> list = stringToList(expression);
//获取到后缀表达式的集合
List<String> list1 = turnInfixToPostfix(list);
//计算后缀表达式
String s = computePostfixExpression(list1);
//遍历中缀表达式集合
System.out.println(expression + " = "+s);
}
//计算后缀表达式集合
public static String computePostfixExpression(List<String> list){
//创建一个数据栈:存储数据
Stack<String> stack = new Stack<>();
//遍历后缀表达式集合
for (String ss :list){
//如果是一个或多个数字的话
if(ss.matches("\\d+")){
//直接压入栈中
stack.push(ss);
}
//该字符串为运算符
else{
//栈顶数据
int num2 = Integer.parseInt(stack.pop());
//次栈顶数据
int num1 = Integer.parseInt(stack.pop());
//两个数据使用运算符进行计算
int num = computeByOperator(num1, num2, ss);
//将计算的结果压入到数据栈中
stack.push(num+"");
}
}
return stack.peek();
}
//计算两个数据的和
private static int computeByOperator(int a, int b, String operator){
switch (operator){
case "+":
return a + b;
case "-":
return a - b;
case "*":
return a * b;
case "/":
return a / b;
default:
throw new RuntimeException("运算符输入有误!");
}
}
//中缀表达式转换为后缀表达式
public static List<String> turnInfixToPostfix(List<String> list){
//创建一个后缀表达式的集合:用来存储得到的后缀表达式
List<String> postfixList = new ArrayList<>();
//运算符栈
Stack<String> operatorStack = new Stack<>();
//遍历中缀表达式的集合
for (String ss : list){
//是数字串:直接添加到后缀表达式集合中
if(ss.matches("\\d+")){
postfixList.add(ss+"");
}
//如果直接是左括号,直接压入到运算符栈中
else if("(".equals(ss)){
operatorStack.push(ss);
}
//如果是右括号,进行计算,直到遇到小括号
else if(")".equals(ss)){
//如果是右括号,遍历运算符栈,直到遇到左括号
while (!"(".equals(operatorStack.peek())){
postfixList.add(operatorStack.pop());
}
//将最左边的左括号进行出栈
operatorStack.pop();
}else{
//左括号的运算符,优先级为0,为最小值
//如果运算符栈栈顶优先级大于当前运算符优先级,栈顶运算符添加到后缀表达式集合中
while (operatorStack.size()!=0 && operator.getOperatorPriority(operatorStack.peek()) >= operator.getOperatorPriority(ss)){
postfixList.add(operatorStack.pop());
}
//将当前运算字符串压入到栈中
operatorStack.push(ss);
}
}
//遍历运算符栈剩余的运算符
while (operatorStack.size()!=0){
postfixList.add(operatorStack.pop());
}
//返回后缀表达式集合
return postfixList;
}
//字符串转换为list集合
public static List<String> stringToList(String str){
//创建一个list数组
List<String> list = new ArrayList<>();
//用来判断是否连续输入数字
boolean flag = false;
//遍历当前字符串
for (int i = 0; i < str.length(); i++){
char ch = str.charAt(i);
//如果是数字
if(isDigit(ch)){
//判断是否出现连续数字情况
if(flag){
//读取最后一个数字
int data = Integer.parseInt(list.get(list.size()-1));
//该数字*10+下一位数字
data = data * 10 + ch - '0';
//更改原来数字的值:更改为连续数字的值
list.set(list.size()-1, data+"");
//跳过这次循环
continue;
}
else{
//直接放到集合中
list.add(ch+"");
}
//连续标志为设置为true
flag = true;
}
//该字符为运算符
else{
//数字连续标志位位false
flag = false;
//直接将运算符添加到集合中
list.add(ch+"");
}
}
//返回list集合
return list;
}
//判断该字符是否为数字字符
private static boolean isDigit(char ch){
return ch >= '0' && ch <= '9';
}
}
class operator{
//加法运算符优先级
private static final int SUM_PRIORITY = 1;
//减法运算符优先级
private static final int SUB_PRIORITY = 2;
//乘法运算符优先级
private static final int MUL_PRIORITY = 2;
//除法运算符优先级
private static final int DIV_PRIORITY = 2;
//获取运算符的优先级
public static int getOperatorPriority(String data){
int result = 0;
switch (data){
case "+":
result = SUM_PRIORITY;
break;
case "-":
result = SUB_PRIORITY;
break;
case "*":
result = MUL_PRIORITY;
break;
case "/":
result = DIV_PRIORITY;
break;
default:
break;
}
return result;
}
}
希望本篇文章对大家有所帮助,后续会继续分享java数据结构与算法相关学习知识…
如果文章内容有错误的地方,请在留言处留下你的见解,方便大家共同学习。谢谢!
如有侵权或其他任何问题请联系:QQ1370922071,本文主要用于学习交流,转载请声明!
作者:皮皮猫吖