K:逆波兰算法
相关介绍:
一种求解字符串形式的表达式的结果的算法,该算法在求解时,需要先将我们平日里习惯上使用的中序表达式的模式转化为等价的后序(后缀)表达式的模式,之后再通过求解出该后序(后缀)表达式的结果而得到原中序表达式的结果,为此,该算法主要有两个任务。第一是将中序表达式转化为后序(后缀)表达式的形式。第二是通过求解后序(后缀)表达式的结果,从而得到原中序表达式的结果。
所谓的中序表达式,就是将运算符放在两个操作数的中间,就是我们平日里所使用的算数表达式的形式,如:1+2+(34+1)*23,10-2等,而后序表达式就是将运算符放在两个操作数之后,如中序表达式A+(B-C/D)*E对应的后序(后缀表达式)为ABCD/-E*+。由于运算符有优先级,为此在计算机内部使用中序表达式来描述时,对计算是非常不方便的,特别是带括号时更加麻烦。而后序表达式中既无运算符优先级,又无括号的约束问题因此在后序表达式中运算符出现的顺序正是计算的顺序,所以计算一个后序表达式比计算一个中序表达式的值简单得多。
中序表达式转化为后序表达式:
由于中序表达式与后序表达式中的操作数所出现的先后次序是完全一样的,只是运算符出现的先后次序不同,为此,将中序表达式转化为后序表达式,其重点是放在运算符的处理上。首先设定运算符的优先级如下表所示:
运算符 | +(加)、-(减) | *(乘)、/(除) |
---|---|---|
优先级 | 1 | 2 |
其中,其数值越大的表示其运算符的优先级越高。 |
要使其运算符出现的次序与真正的算术运算符顺序一致,就要使优先级高的以及括号内的运算符先出现在前面,所以在把算术表达式转化为后序表达式的时候,要使用一个栈来保留还未送往后缀表达式的运算符,此栈称为运算符栈。其算法的基本思想如下:
- 初始化一个运算符栈
- 从算术表达式输入的字符串中从左到右读取一个字符
- 若当前字符是操作数,则直接送往后缀表达式
- 若当前字符是左括号“(”,则将其压入运算符栈
- 若当前字符为运算符时,则
- 当运算符栈为空时,将该运算符直接压入运算符栈中
- 当此运算符的优先级高于栈顶元素时,将此运算符压入运算符栈;否则,弹出栈顶运算符送往后序表达式,并将当前运算符压栈,重复步骤5
- 若当前字符是右括号")"时,反复将栈顶符号弹出,并送往后序表达式,直到栈顶运算符为左括号"("为止,再将左括号出栈并丢弃
- 若读取未完成,则跳转到步骤2
- 若读取完毕,则将栈中剩余的所有运算符弹出栈,并送往后序表达式中
其实现代码如下:
public class ReverseBoLan
{
//该符号表用于定义运算符的优先级
private Map<String,Integer> table=getMap();
//该字符串为用于匹配数字的正则表达式
private String rexNumber="\\d+";
private String rexOperator="[((+\\-*/×÷))]";
//该字符串为用户输入的中序表达式
private String input;
//该栈对象用于存储运算符和括号(左括号)
private Stack<String> save=new Stack<String>();
/**
* 该方法用于将中序表达式转化为后序表达式,并对其转化后的表达式以字符串的形式进行返回
* @return 后序表达式
*/
public String reverse()
{
//result变量用于存储结果
String result="";
//将输入的中序表达式中的数字存储到number数组中
String[] number=input.split(rexOperator);
int order=0;
int i=0;
while(i<input.length())
{
//获得当前字符
String thisString=String.valueOf(input.charAt(i));
//当前该字符为运算符或者括号时,即当前该字符不为数字时
if(thisString.matches(rexOperator))
{
//当当前字符不为左括号或者右括号时(即为运算符)
if(!thisString.matches("[()()]"))
{
//用于记录栈顶元素的优先级
int temporary=0;
//获取当前字符的优先级
int present=table.get(thisString);
//当操作数的栈不为空的时候
if(!save.isEmpty())
{
//查看栈顶元素的字符以及其优先级
String top=save.getTop();
if(!top.matches("[((]"))
{
temporary=table.get(top);
}
}
//当栈顶元素的操作符的优先级比当前操作符的优先级还要高或者相同时,对其进行弹出操作,直到栈顶元素的优先级比当前操作符的优先级要低
if(temporary>=present)
{
while(!save.isEmpty()&&table.get(save.getTop())>=present)
{
result+=" "+save.pop();
}
}
save.push(thisString);
}
//当当前的字符为左括号的时候,直接将其压入栈中
else if(thisString.matches("[((]"))
{
save.push(thisString);
}
//当当前的字符为右括号的时候,将其栈中的元素一直弹出,直至遇到左括号结束,并将左括号弹出
else
{
while(!save.getTop().matches("[((]"))
result+=" "+save.pop();
save.pop();
}
i++;
}
//当前该字符为数字的时候
if(thisString.matches(rexNumber))
{
//用于存储数字数组中的数字字符串
String numberString=null;
do
{
numberString=number[order];
//当数字字符串中的数字不为空时(由于可能会是空字符串的出现),将整个中序表达式的字符串的指针进行向右移动
if(!numberString.trim().equals(""))
{
i+=numberString.length();
order++;
break;
}
else
{
order++;
}
}while(true);
result+=" "+numberString;
}
}
//将栈中剩余的字符进行弹出
while(!save.isEmpty())
{
result+=" "+save.pop();
}
return result;
}
/**
* 该方法用于设置实例变量input的值
* @param input 为中序表达式
*/
public void setInput(String input)
{
//去掉输入的字符串中存在的所有的空字符
input=input.replaceAll(" ", "");
//去掉中序表达式字符串中各种杂七杂八的字符
input=input.replaceAll("[^+\\-*/×÷\\.\\d()()]", "");
this.input=input;
}
//该方法用于实现一个符号表
private static Map<String,Integer> getMap()
{
Map<String,Integer> temp=new HashMap<String,Integer>();
//定义各个运算符的优先级,其中,x和÷字符用于兼容
temp.put("*", 2);
temp.put("/", 2);
temp.put("×",2);
temp.put("÷",2);
temp.put("+", 1);
temp.put("-",1);
return temp;
}
}
计算后序表达式的结果:
计算一个后序表达式的值比较简单,只要先找到运算符,再去找前面最后出现的两个操作数,从而构成一个较小的算术表达式进行运算,在计算过程中,也需要利用一个栈来保留后序表达式中还未参与运算的操作数,此栈称之为操作数栈。计算后序表达式值的算法的基本实现如下:
- 初始化一个操作数栈
- 从左到右依次读入后序表达式中的字符:
- 若当前字符是操作数,则压入操作数栈中
- 若当前字符是运算符,则从栈顶弹出两个操作数并分别作为第2个操作数和第1个操作数参与运算,再将运算结果压入操作数栈中
- 重复步骤2,直到读入的后序表达式结束为止,则操作数栈中的栈顶元素即为后序表达式的计算结果
其实现的关键代码如下:
/**
* 该方法用于将后序表达式的结果进行计算,得出其最终结果
* 返回最终计算的结果。
* 输入的参数为前面带空格的,数字和运算符之间带空格的后序表达式
*
*/
public double counter(String input)
{
//去掉其首尾空格
input=input.trim();
//该栈用于存储下数字
Stack<String> numbers=new Stack<String>();
//用于记录下数字使用
String n="";
//用于循环遍历每一个字符
for(int i=0;i<input.length();i++)
{
String ch=String.valueOf(input.charAt(i));
//当前的字符为空字符的时候,遍历下一个字符
if(ch.equals(" "))
{
//将数字字符串存入栈中并遍历下一个字符
numbers.push(n);
n="";
continue;
}
//当为数字的时候,对其进行组装,将其组装成字符串
else if(ch.matches("[0-9\\.]"))
{
n+=ch;
}
//当其为运算符的时候,对其栈中的前两个数字字符串进行弹出,并将运算结果压入栈中
else
{
switch(ch)
{
case "×":
case "*":
{
double two=Double.parseDouble(numbers.pop());
double one=Double.parseDouble(numbers.pop());
double result=one*two;
n=String.valueOf(result);
break;
}
case "÷":
case "/":
{
double two=Double.parseDouble(numbers.pop());
double one=Double.parseDouble(numbers.pop());
double result=one/two;
n=String.valueOf(result);
break;
}
case "+":
{
double two=Double.parseDouble(numbers.pop());
double one=Double.parseDouble(numbers.pop());
double result=one+two;
n=String.valueOf(result);
break;
}
case "-":
{
double two=Double.parseDouble(numbers.pop());
double one=Double.parseDouble(numbers.pop());
double result=one-two;
n=String.valueOf(result);
break;
}
}
}
}
//将最后计算的结果放入栈中
numbers.push(n);
double result=Double.parseDouble(numbers.pop());
return result;
}
【推荐】还在用 ECharts 开发大屏?试试这款永久免费的开源 BI 工具!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 软件产品开发中常见的10个问题及处理方法
· .NET 原生驾驭 AI 新基建实战系列:向量数据库的应用与畅想
· 从问题排查到源码分析:ActiveMQ消费端频繁日志刷屏的秘密
· 一次Java后端服务间歇性响应慢的问题排查记录
· dotnet 源代码生成器分析器入门
· ThreeJs-16智慧城市项目(重磅以及未来发展ai)
· .NET 原生驾驭 AI 新基建实战系列(一):向量数据库的应用与畅想
· Ai满嘴顺口溜,想考研?浪费我几个小时
· Browser-use 详细介绍&使用文档
· 软件产品开发中常见的10个问题及处理方法