前缀表达式、中缀表达式、后缀表达式
1.前缀表达式
前缀表达式是一种没有括号的算术表达式,与中缀表达式不同的是,其将运算符写在前面,操作数写在后面。为纪念其发明者波兰数学家Jan Lukasiewicz,前缀表达式也称为“波兰式”。例如,- 1 + 2 3,它等价于1-(2+3)。
例如,(a+b)*(c+d)转换为*,+,a,b,+,c,d。
后面的前缀表达式的运算方式为:如果当前字符(或字符串)为数字或变量,则压入栈内;如果是运算符,则将栈顶两个元素弹出栈外并作相应运算,再将结果压入栈内。当前缀表达式扫描结束时,栈里的就是中缀表达式运算的最终结果。对比中缀运算的步骤,不难发现前缀运算在计算机上的优势。
对前缀表达式求值,要从右至左扫描表达式,首先从右边第一个字符开始判断,若当前字符是数字则一直到数字串的末尾再记录下来,若为运算符,则将右边离得最近的两个“数字串”作相应运算,然后以此作为一个新的“数字串”并记录下来;扫描到表达式最左端时扫描结束,最后运算的值即为表达式的值。
例如:对前缀表达式“- 1 + 2 3”求值,扫描到3时,记录下这个数字串,扫描到2时,记录下这个数字串,当扫描到+时,将+右移做相邻两数字串的运算符,记为2+3,结果为5,记录下5这个新数字串,然后继续向左扫描,扫描到1时,记录下这个数字串,扫描到-时,将-右移做相邻两数字串的运算符,记为1-5,结果为-4,此时关于这个表达式的全部运算已完成,故表达式的值为-4。
中缀表达式转化为前缀表达式的例子:
a+b ---> +,a,b
a+(b-c) ---> +,a,-,b,c
a+(b-c)*d ---> +,a,*,-,b,c,d
a+1+3 ---> +,+,a,1,3
(1) 首先构造一个运算符栈(也可放置括号),运算符(以括号为分界点)在栈内遵循越往栈顶优先级不降低的原则进行排列。
(2)从右至左扫描中缀表达式,从右边第一个字符开始判断:
如果当前字符是数字,则分析到数字串的结尾并将数字串直接输出。
如果是运算符,则比较优先级。如果当前运算符的优先级大于等于栈顶运算符的优先级(当栈顶是括号时,直接入栈),则将运算符直接入栈;否则将栈顶运算符出栈并输出,直到当前运算符的优先级大于等于栈顶运算符的优先级(当栈顶是括号时,直接入栈),再将当前运算符入栈。
如果是括号,则根据括号的方向进行处理。如果是向右的括号,则直接入栈;否则,遇向左的括号前将所有的运算符全部出栈并输出,遇右括号后将向左、向右的两括号一起出栈(并不输出)。
(3) 重复上述操作(2)直至扫描结束,将栈内剩余运算符全部出栈并输出,再逆缀输出字符串。中缀表达式也就转换为前缀表达式了。
将中缀表达式“1+((2+3)*4)-5”转换为前缀表达式。
中缀表达式
|
前缀表达式
|
(栈尾)运算符栈(栈顶)
|
说明
|
5
|
5
|
空
|
5,是数字串直接输出
|
-
|
5
|
-
|
-,栈内无运算符,直接入栈
|
)
|
5
|
-)
|
),直接入栈
|
4
|
5 4
|
-)
|
4,是数字串直接输出
|
*
|
5 4
|
-)*
|
*,栈顶是括号,直接入栈
|
)
|
5 4
|
- ) * )
|
),直接入栈
|
3
|
5 4 3
|
- ) * )
|
3,是数字串直接输出
|
+
|
5 4 3
|
- ) * ) +
|
+,栈顶是括号,直接入栈
|
2
|
5 4 3 2
|
- ) * )+
|
2,是数字串直接输出
|
(
|
5 4 3 2+
|
- ) *
|
(,与栈里最后一个)抵消,并释放它们之间的+
|
(
|
5 4 3 2+*
|
-
|
(,方法与上类同,请参考下一目录
|
+
|
5 4 3 2+*
|
-+
|
+,优先级大于等于栈顶运算符,直接入栈
|
1
|
5 4 3 2+*1
|
-+
|
1,是数字串直接输出
|
空
|
5 4 3 2+*1+-
|
空
|
扫描结束,将栈内剩余运算符全部出栈并输出
|
空
|
- + 1 * + 2 3 4 5
|
空
|
逆缀输出字符串
|
对运算符的具体处理方法如下:
):直接入栈
(:遇)前,将运算符全部出栈并输出;遇)后,将两括号一起删除①
+、-:1
*、/、%:2
^:3
2.后缀表达式
一个表达式E的后缀形式可以如下定义:
(2)如果E是E1 op E2形式的表达式,这里op是任何二元操作符,则E的后缀式为E1'E2' op,这里E1'和E2'分别为E1和E2的后缀式。
(3)如果E是(E1)形式的表达式,则E1的后缀式就是E的后缀式。
(a+b)*c-(a+b)/e的后缀表达式为:
(a+b)*c-(a+b)/e
→((a+b)*c)((a+b)/e)-
→((a+b)c*)((a+b)e/)-
→(ab+c*)(ab+e/)-
→ab+c*ab+e/-
将一个普通的中缀表达式转换为逆波兰表达式的一般算法是:
首先需要分配2个栈,一个作为临时存储运算符的栈S1(含一个结束符号),一个作为输入逆波兰式的栈S2(空栈),S1栈可先放入优先级最低的运算符#,注意,中缀式应以此最低优先级的运算符结束。可指定其他字符,不一定非#不可。从中缀式的左端开始取字符,逐序进行如下步骤:
(1)若取出的字符是操作数,则分析出完整的运算数,该操作数直接送入S2栈
(2)若取出的字符是运算符,则将该运算符与S1栈栈顶元素比较,如果该运算符优先级(不包括括号运算符)大于S1栈栈顶运算符优先级,则将该运算符进S1栈,否则,将S1栈的栈顶运算符弹出,送入S2栈中,直至S1栈栈顶运算符低于(不包括等于)该运算符优先级,最后将该运算符送入S1栈。
(3)若取出的字符是“(”,则直接送入S1栈顶。
(4)若取出的字符是“)”,则将距离S1栈栈顶最近的“(”之间的运算符,逐个出栈,依次送入S2栈,此时抛弃“(”。
(5)重复上面的1~4步,直至处理完所有的输入字符
(6)若取出的字符是“#”,则将S1栈内所有运算符(不包括“#”),逐个出栈,依次送入S2栈。
完成以上步骤,S2栈便为逆波兰式输出结果。不过S2应做一下逆序处理。便可以按照逆波兰式的计算方法计算了!
代码实现
public class PolandNotation { public static void main(String[] args) { //创建逆波兰表达式 //(3+4)*5-6 => 3 4 + 5 * 6 - /* String suffixExpression= "3 4 + 5 * 6 -"; *//*1.将3 4 + 5 * 6 - =》放到List中 * 2.将list利用方法遍历list配合栈进行计算*//* List<String> rpnlist = getList(suffixExpression); System.out.println("rpnlist=" +rpnlist); int res = calculation(rpnlist); System.out.printf("计算结果: %d\n",res);*/ /*将中缀表达式转换成后缀表达式*/ //1+((2+3)*4)-5 ====>1 2 3 + 4 * + 5 - /*将字符串转换成中缀的list*/ String expression = "1+((2+3)*4)-5"; List<String> infixExpressionList = toInfixExpressionList(expression); System.out.println("后缀表达式对应的List=" + infixExpressionList); List<String> parseSuffixExpressionList = parseSuffixExpressionList(infixExpressionList); //结果List=[1, +, (, (, 2, +, 3, ), *, 4, ), -, 5] System.out.println("后缀表达式对应的List= " + parseSuffixExpressionList); //结果 List= [1, 2, 3, +, 4, *, +, 5, -] int calculation = calculation(parseSuffixExpressionList); System.out.printf("计算结果为:%d\n",calculation);//=16 } /*将字符串转换成中缀的list*/ public static List<String> toInfixExpressionList(String s){ List<String> ls= new ArrayList<>(); int i=0;//指针:遍历中缀字符串 String str;//多位数拼接 char c;//保存字符 放入c; do { //c是数字:进行字符串拼接 if ((c=s.charAt(i))<48 || (c=s.charAt(i))>57){ ls.add(""+c); i++; }else {//考虑多位数问题 str="";// while (i<s.length()&&(c=s.charAt(i))>=48 && (c=s.charAt(i))<=57){ str+=c; i++; } ls.add(str); } }while (i<s.length()); return ls; } /*中缀表达式转成后缀表达式*/ public static List<String> parseSuffixExpressionList(List<String> ls){ Stack<String> s1 = new Stack<>(); //符号栈 //第二个栈不需要pop操作,而且需要逆向输出所以利用list代替 List<String> s2 = new ArrayList<>(); for (String item : ls) { /*如果是数入s1*/ if (item.matches("\\d+")){ s2.add(item); }else if (item.equals("(")){ s1.push(item); }else if (item.equals(")")){ /*依次弹出s1栈顶的运算符,并压入s2,知道遇到左括号为止,此时将左括号丢弃*/ while (!s1.peek().equals("(")){ s2.add(s1.pop()); } s1.pop();//将左括号丢弃消除小括号 }else { /*当item的优先级小于等于s1栈顶的运算符的优先级 * 1.将s1的运算符弹出并加入到s2中,依次比较新的运算符 * 方法比较运算符的优先级的方法*/ while (s1.size()!=0 && Operation.getValue(s1.peek())>=Operation.getValue(item)){ s2.add(s1.pop()); } //将item压入栈中 s1.push(item); } } /*将s1剩余的运算符压入栈中*/ while (s1.size()!=0){ s2.add(s1.pop()); } return s2;//存放到list,因此按顺序输出就是逆波兰表达式 } /*将逆波兰表达式放入到list*/ public static List<String> getList(String suffixExpression){ //字符串分割 String[] split = suffixExpression.split(" "); List<String> list = new ArrayList<>(); for (String ele : split) { list.add(ele); } return list; } /*完成逆波兰运算*/ public static int calculation(List<String> ls){ //创建一个栈 Stack<String> stack = new Stack<String>(); for (String item : ls) { /*使用正则表达式*/ if (item.matches("\\d+")){ stack.push(item);//入栈 }else { int num2 =Integer.parseInt(stack.pop());//取出pop2个数据进行运算再入栈 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("运算符号错误"); } //将结果入栈 stack.push(res+""); } } //留着栈中的就是计算结果 return Integer.parseInt(stack.pop()); } }