栈的应用–四则运算表达式求值
1、后缀(逆波兰)表示法的定义
一种不需要括号的后缀表达法
例:
(1)对于 “9+(3-1)3+10/2” 这种传统的表达式我们称之为中缀表达式*,顾名思义,后缀表达式就是把所有的运算符全部表示在需要计算的数字后面(不包括括号)。
那对于 “9+(3-1)*3+10/2”的后缀表达式为“9 3 1 - 3 * + 10 2 / +”。
(2)后缀表达式我们要怎么计算呢?
①我们初始化一个空栈,将字符串 “9+(3-1)3+10/2” 转化为字符数组*[‘9’ , ‘+’ , ‘(’ , ‘3’ , ‘-’ , ‘1’ , ‘)’ , ‘X’ , ‘3’ , ‘+’ , ‘10’ , ‘/’ , ‘2’]**。
②循环遍历数组,若为数字,便输出。
③若不是数字,则判断是否为左括号,若为左括号,则进栈。
④若不是左括号,则判断该运算符与栈顶运算符的优先级。运算符优先级为 ‘(’ == ‘)’ < ‘+’ == ‘-’ < ‘*’ == '/'
⑤若栈顶运算符的优先级小于该运算符,则直接进栈。
⑥否则判断该运算符是否为右括号。
⑦若为右括号,则循环弹出栈顶运算符,直到匹配到左括号停止循环。
⑧若不是右括号,则循环弹出栈顶元素,直到栈顶元素优先级小于该运算符或者栈为空时停止。再将该运算符入栈。
图例
①初始化空栈
第一个元素为 9 ,直接输出 9。
第二个元素为 + ,此时栈为空,所以直接入栈
第三个元素为 ( ,直接入栈
第四个元素为 3 ,输出 3 ,此时输出为 9 3。
第五个元素为 - ,- 的优先级 大于 ( , 入栈
第六个元素为 1 ,输出 1,此时输出为 9 3 1。
第七个元素为 ) ,循环出栈栈顶元素,直到匹配到 ( 为止,此时输出结果为 9 3 1 - ,输出之后栈为
第八个元素为 * ,* 的优先级大于栈顶元素 + ,直接入栈
第九个元素为 3 ,直接输出 3 ,此时输出为 9 3 1 - 3。
第十个元素为 + ,+ 的优先级小于 * ,则循环弹出栈顶元素,直到栈顶元素的优先级小于 + 或者栈为空时停止,此时输出为 9 3 1 - 3 * + ,栈为空循环停止,第十个元素 + 入栈
第十一个元素为 10,直接输出 10,此时输出为 9 3 1 - 3 * + 10。
第十二个元素为 / ,/ 的优先级大于 + ,直接入栈,不输出
第十三个元素为 2 ,直接输出 2,此时输出为 9 3 1 - 3 * + 10 2。
至此数组遍历完毕,弹出栈中剩余元素即可,输出为 9 3 1 - 3 * + 10 2 / + 。
2、计算后缀表达式
相比于求后缀表达式,后缀表达式的计算要容易许多。
首先把我们得到的后缀表达式“9 3 1 - 3 * + 10 2 / +”分解为数组[ ‘9’ , ‘3’ , ‘1’ , ‘-’ , ‘3’ , ‘*’ , ‘+’ , ‘10’ , ‘2’ , ‘/’ , ‘+’]。
创建一个空栈。
循环遍历数组,若为数字则入栈。
若为运算符,则取两次栈顶元素,进行计算(注意:当运算符为 ‘-’ 或 ‘/’ 时,第一个弹出的栈顶元素为减数(除数),第二个为被减数(被除数)),并将计算结果入栈。
继续以上述表达式为例进行分析:
图例
初始化一个空栈
数组第一个元素为 9 ,入栈
数组第二个元素为 3,入栈
第三个元素为 1 ,入栈
第四个元素为 ’ - ‘,弹出两个栈顶元素计算 3-1 ==2 ,将计算结果入栈
第五个元素为 3 ,入栈
第六个元素为 *,弹出两个元素计算 3 * 2==6,将计算结果入栈
第七个元素为 +,弹出两个元素计算 9 + 6 == 15,将计算结果入栈
第八个元素为 10,入栈
第九个元素为 2,入栈
第十个元素为 / ,弹出两个栈顶元素,计算 10 / 2 == 5,并将计算结果入栈
第十一个元素为 + ,弹出两个栈顶元素,计算 15+5==20,并将结果入栈
此时数组为空,弹出栈顶元素即为结果 20。
3、代码实现
import com.sun.javaws.IconUtil;
import java.util.Scanner;
import java.util.Stack;
public class Test {
public static void main(String[] args) {
String s = null;
Scanner in = new Scanner(System.in);
System.out.println("请输入您要计算的表达式:");
s = in.nextLine();
try{
s = getPostfixExpression(s);
System.out.println("您得到的后缀表达式为:"+s);
System.out.println("经计算,值为"+countPostfixExpression(s));
}catch (Exception e){
System.out.println("请检查您输入的表达式是否正确");
}
}
//计算后缀表达式
static int countPostfixExpression(String exp) throws Exception {
// 利用转化时添加的空格进行分割,得到数组
String []exps = exp.split(" ");
// 创建数字栈
Stack numStack = new Stack();
// 遍历数组
for ( String e : exps ) {
// 判断元素是否为运算符
// 当元素为运算符时,去出数字栈内前两个元素进行相应计算
// 若输入的表达式没问题,则在遇见运算符时,数字栈中必有两个或两个以上元素,否则即抛出异常
// 计算完成后需将计算结果入栈,参与后续计算
if(e.equals("+")){
if(numStack.size()<2){
throw new Exception();
}
numStack.push((int)numStack.pop()+(int)numStack.pop());
}else if(e.equals("-")){
if(numStack.size()<2){
throw new Exception();
}
numStack.push(-((int) numStack.pop()-(int)numStack.pop()));
}else if(e.equals("*")){
if(numStack.size()<2){
throw new Exception();
}
numStack.push((int)numStack.pop()*(int)numStack.pop());
}else if(e.equals("/")){
if(numStack.size()<2){
throw new Exception();
}
int x = (int)numStack.pop();
int y = (int)numStack.pop();
if(x == 0){
throw new Exception();
}
numStack.push(y/x);
}else{
// 将数字入栈
numStack.push(Integer.parseInt(e));
}
}
return (int)numStack.pop();
}
// 获取后缀表达式
static String getPostfixExpression(String exp) throws Exception{
// 将字符串解析为数字
char[] exps = exp.toCharArray();
// 判断表达式是否以数字开头,不是则判定为不合法,且抛出异常
if(!('0'<=exps[0]&&exps[0]<='9')){
throw new Exception();
}
// 创建两个栈,用来存储运算符与数字
Stack expsStack = new Stack();
Stack numStack = new Stack();
String result = "";
// 循环遍历数组元素
for (char c : exps) {
// 消除表达式中的空格
if(c==' '){
continue;
}
// 判断元素是否为数字
if('0'<=c && c <= '9'){
// 当元素为数字且当前数字栈为空时,直接入栈。
if(numStack.empty()){
numStack.push(((int)c)%48);
}else{
// 当数字栈不为空时,将数字栈中元素取出并乘以10
// 因为将字符串分割为字符数组时,类似于13这样的数字会被拆解为‘1’,‘3’两个元素
// 当两个数字字符连续时,我们就认为它是一个多位数被拆分,数字栈里永远只有一个元素
// 当检测到运算符时,弹出数字栈中的元素即为正确数字
int x =((int)numStack.pop())%48;
x = x*10 + ((int)c)%48;
numStack.push(x);
}
}else{
// System.out.println(numStack);
// 检测到运算符,判断数字栈是否为空,不为空则弹出数字
if(!numStack.empty()){
// 数字与运算符之间添加空格,方便后续计算
result = result + numStack.pop() + " ";
}
// 检测运算符栈是否为空
if(expsStack.empty()){
// 判断输入的运算符是否合法,此程序中,只允许输入 +、-、*、/、(、)等,输入不正确的运算符就抛出异常
if(getPriority(c)==-1){
throw new Exception();
}
// 运算符合法,且运算符栈为空,直接入栈
expsStack.push(c);
}else{
// 运算符栈不为空时
// 检测运算符合法性,不合法抛出异常
if(getPriority(c)==-1){
throw new Exception();
}else if(getPriority((char)expsStack.peek()) < getPriority(c)){
// 运算符合法时,比较栈顶运算符与输入运算符的优先级,若输入运算符优先级大于栈顶运算符优先级
// 直接入栈
expsStack.push(c);
}else{
// 否则判断是否为左括号
// 运算符优先级顺序为 ( = ) < + = - < * = /
if(c == '('){
// 左括号直接入栈(匹配到右括号时出栈)
expsStack.push(c);
}else if(c == ')'){
// 当元素安抚为右括号时
// 循环出栈运算符,直到匹配到第一个左括号
while((char)expsStack.peek() != '('){
// System.out.println(expsStack.pop());
result = result + (char)expsStack.pop() + " ";
}
// 弹出匹配完成的左括号
expsStack.pop();
}else{
// 若运算符不是括号,则循环出栈运算符,直到栈顶运算符优先级 < 输入运算符
while(getPriority((char)expsStack.peek()) >= getPriority(c)){
// System.out.println((char)expsStack.pop());
result = result + (char)expsStack.pop() + " ";
// 当栈为空时,跳出循环
if(expsStack.empty()){
break;
}
}
// 将输入运算符入栈
expsStack.push(c);
}
}
}
}
}
// 输出数字栈所有元素
while(!numStack.empty()){
result = result + numStack.pop() + " ";
}
// 输出运算符栈所有元素
while(!expsStack.empty()){
// System.out.println(expsStack.pop());
result = result + (char)expsStack.pop() + " ";
}
// 返回转换的后缀表达式
return result;
}
// 获取运算符优先级
static int getPriority(char exp){
int pri = -1;
if(exp == '+' || exp == '-'){
pri = 1;
}else if(exp == '*' || exp == '/'){
pri = 2;
}else if(exp == '(' || exp == ')'){
pri = 0;
}
return pri;
}
}