数据结构(九)栈的作用--逆波兰表达式
一、逆波兰表达式的定义
算数表达式是由操作数、算数运算符和分隔符所组成的式子。
表达式一般有中缀表达式、后缀表达式和前缀表达式,其中,中缀表达式是将运算符放在两个操作数的中间,后缀表达式(也称逆波兰表达式)是将运算符放在两个操作数之后,而前缀表达式是将运算符放在两个操作数之前。
例如,中缀表达式A+(B-C/D)*E,对应的后缀表达式为ABD/-E*+,对应的前缀表达式为+A*-B/CDE。
逆波兰表达式就是一种不需要括号的后缀表示法。
由于运算符有优先级,所以在计算机内部使用中缀表达式描述时,对计算是非常不方便的,特别是带括号时更麻烦。而后缀表达式中既无运算符优先级又无括号的约束问题,同时后缀表达式中运算符出现的顺序正是计算的顺序,所以计算一个后缀表达式的值要比计算一个中缀表达式的值简单得多。
二、将原算数表达式转换成后缀表达式
1.算法思路
由于原算术表达式与后缀表达式中的操作数所出现的先后次序是完全一样的,只是运算符出现的先后次序不一样,所以转换的重点放在运算符的处理上。
首先设定运算符的优先级:0(左括号)、1(加、减)、2(乘、除、取模)、3(幂)
实现算法的思路为:
(1)初始化一个运算符栈
(2)从算数表达式的输入字符串中从左到右读取一个字符
(3)若当前字符是操作数,则直接送往后缀表达式
(4)若当前字符是左括号时,将其压进运算符栈
(5)若当前字符是运算符时,则:
①当运算符栈为空,则将其压入运算符栈
②当运算符的优先级高于栈顶运算符,则将此运算符压入运算符栈;否则弹出栈顶运算符送往后缀表达式,并将当前运算符压栈,重复(5)
(6)当当前字符是右括号时,反复将栈顶符号弹出,并送往后缀表达式,直到栈顶符号是左括号为止,再将左括号出栈并丢弃
(7)若读取还未完毕,则跳转到(2)
(8)若读取完毕,则将栈中剩余的所有运算符弹出并送往后缀表达式
2.算法实现
package bigjun.iplab.ReversePolish; /** * 逆波兰表达式(包括将算术表达式转化成逆波兰表达式和计算逆波兰表达式的值) */ public class ReversePolish { public String convertToPostfix(String expression){ LinkStack S = new LinkStack(); // 初始化一个运算符栈 String postfix = new String(); // 初始化一个用于存放输出的后缀表达式的字符串 for (int i = 0; i < expression.length() && expression != null; i++) { char c = expression.charAt(i); // 从算数表达式中依次读取字符 if (c != ' ') { // 字符c不为空格 if (isLeftParenthesis(c)) { // 字符c为左括号的情况 S.Push(c); // 左括号直接入栈 } else if (isRightParenthesis(c)) { // 字符c为右括号的情况 char ac = (Character) S.Pop(); while (!isLeftParenthesis(ac) && !S.isEmpty()) { // 一直到匹配到左括号为止 postfix = postfix.concat(String.valueOf(ac));// 将栈中左括号前面的运算符都输出 ac = (Character) S.Pop(); // 最后将左括号也弹出栈 } } else if (isOperator(c)) { // 字符c为运算符的情况 if (! S.isEmpty()) { Object ac = (char) S.Pop(); // 如果栈顶的运算符的优先级比运算符c的优先级高,则将栈顶元素输出到后缀表达式 while (ac != null && priority((char) ac) >= priority(c)) { postfix = postfix.concat(String.valueOf(ac)); ac = S.Pop(); } if (ac != null) { // 将最后一次出栈的优先级没有高于运算符c的优先级的运算符再次入栈 S.Push(ac); } } S.Push(c); // 将字符c入栈 } else { // 字符c为数字的情况 postfix = postfix.concat(String.valueOf(c)); // 直接输出 } } } while (!S.isEmpty()) { postfix = postfix.concat(String.valueOf(S.Pop())); } return postfix; } private boolean isLeftParenthesis(char c) { return '(' == c; } private boolean isRightParenthesis(char c) { return ')' == c; } private boolean isOperator(char c) { if ('+' == c || '-' == c || '*' == c || '/' == c || '%' == c || '^' == c) { return true; } else { return false; } } private int priority(char c) { if (c == '^') { return 3; } if ('*' == c || '/' == c || '%' == c) { return 2; } else if ('+' == c || '-' == c) { return 1; } else { return 0; } } }
3.分析算法执行过程
public static void main(String[] args) throws Exception { ReversePolish RS = new ReversePolish(); String postfix = RS.convertToPostfix("9+(3-1)*3+10/2"); System.out.print("将算术表达式转化为后缀表达式为: " + postfix); System.out.println(); } 输出为: 将算术表达式转化为后缀表达式为: 931-3*+102/+ 1.字符为9:直接输出,postfix = 9 2.字符为+:入栈,栈内:+ 3.字符为(:入栈,栈内:+ ( 4.字符为3: 直接输出,postfix = 93 5.字符为-: 入栈,栈内:+ ( - 6.字符为1: 直接输出,postfix = 931 7.字符为): 匹配左括号循环,将栈内-出栈并输出,postfix = 931-,左括号也出栈,栈内:+ 8.字符为*: 栈顶元素+的优先级低于*,入栈,栈内:+ * 9.字符为3: 直接输出,postfix = 931-3 10.字符为+: 栈顶元素出栈并为*,且*的优先级高于+,*输出,postfix = 931-3*,栈顶元素为+出栈,+的优先级等于字符+,+输出,postfix = 931-3*+,栈为空,将+入栈,栈内:+ 11.字符为10:直接输出,postfix = 931-3*+10 12.字符为/: 栈顶元素出栈并为+,优先级低于/,将+重新入栈,将/入栈,栈内:+ / 13.字符为2: 直接输出,postfix = 931-3*+102
14.最后一个数字输出完毕,将栈内所有运算符输出,postfix = 931-3*+102/+
三、计算后缀表达式的值
1.算法思路
要计算后缀表达式的值,只要先找到运算符,再去找前面最后出现的两个操作数,从而构成一个最小的算术表达式进行运算,在计算过程中也需利用一个栈来保留后缀表达式中还未参与运算的操作数。
基本思想如下:
(1)初始化一个操作数栈
(2)从左到右依次读入后缀表达式中的字符:
①若当前字符是操作数,则操作数入栈
②若当前字符是运算符,则从栈顶弹出两个操作数分别作为第2个操作数和第1个操作数参与运算,再将运算结果入栈
(3)重复第(2)步,直到读入的后缀表达式结束为止,则栈顶元素即为计算结果。
2.算法实现
// 计算后缀表达式的值,仅支持计算个位数的数字运算,如果想要计算两位数以上的,可以考虑使用控制台依次输入单个字符 public double postfixCalculate(String postfix) { LinkStack S = new LinkStack(); for (int i = 0; i < postfix.length() && postfix != null; i++) { char c = postfix.charAt(i); // 从后缀表达式中依次读取字符 if (isOperator(c)) { double d2 = Double.valueOf(S.Pop().toString()); double d1 = Double.valueOf(S.Pop().toString()); double result = 0; if (c == '+') { result = d1 + d2; } else if (c == '-') { result = d1 - d2; } else if (c == '*') { result = d1 * d2; } else if (c == '/') { result = d1 / d2; } else if (c == '^') { result = Math.pow(d1, d2); } else if (c == '%') { result = d1 % d2; } S.Push(result); } else { S.Push(c); } } return (double) S.Pop(); }
3.分析算法执行过程
public static void main(String[] args) throws Exception { ReversePolish RS = new ReversePolish(); String postfix1 = RS.convertToPostfix("(1 + 2) * ( 5 - 2 ) / 2 ^ 2 + 5 % 3 "); System.out.print("将算术表达式转化为后缀表达式为: " + postfix1); System.out.println(); System.out.print("计算转换后的后缀表达式的结果为: " + RS.postfixCalculate(postfix1)); } 输出为: 将算术表达式转化为后缀表达式为: 12+52-*22^/53%+ 计算转换后的后缀表达式的结果为: 4.25
(注意,这里省略了小数部分,在进行计算前出栈后的数字都为double型,比如9出栈后为9.0,4出栈后为4.0,计算9.0/4.0才能得到2.25,否则,计算9/4只能得到2.0)
1.字符为1:入栈,栈内:1
2.字符为2:入栈,栈内:1,2
3.字符为+:计算1+2=3,并入栈,栈内:3
4.字符为5:入栈,栈内:3 5
5.字符为2:入栈,栈内:3 5 2
6.字符为-:计算5-2=3,并入栈,栈内:3 3
7.字符为*:计算3*3,并入栈,栈内:9
8.字符为2:入栈,栈内9 2
9.字符为2:入栈,栈内9 2 2
10.字符为^:计算2^2=4,并入栈,栈内9 4
11.字符为/:计算9/4=2.25,并入栈,栈内2.25
12.字符为5:入栈,栈内2.25 5
13.字符为3:入栈,栈内2.25 5 3
14.字符为%:计算5%3=2.0,并入栈,栈内2.25 2.0
15.字符为+:计算2.25+2.0=4.25,并入栈,栈内4.25
16.字符读取完毕,将栈内4.25弹出并输出到结果上。