中缀表达式转后缀表达式---栈--二叉树---四则运算
我们平常书写的四则运算表达式属于中缀表达式,形式为"9+(3-1)*3+10/2",因为所有的运算符号都在两操作数之间,所以称为中缀表达式。我们使用中缀表达式来计算表达式的值,不过这种形式并不适合计算机求解。接下来,我们将中缀表达式转化为后缀表达式,所谓的后缀表达式就是操作符位于操作数后面的不包含括号的算数表达式,也叫做逆波兰表达式。
1)首先介绍一种人工的转化方法(http://www.cnblogs.com/MichaelYin/archive/2012/05/02/2479248.html)。以"9+(3-1)*3+10/2"为例,按照运算的规则,找出首先计算的部分,这部分包含两个操作数和一个操作符,将操作符移动到两个操作数右侧,这就完成了第一部分的转换,将这部分看作一个操作数,按照运算规则,以相同的方法转换,转换过程如下:
2)还可以利用二叉树求得后缀表达式,首先利用中缀表达式构造二叉树,数字是叶子节点,操作符为根节点。每次找到“最后计算”的运算符,作为当前根节点,运算符左侧表达式作为左节点,右侧表达式作为右节点,然后递归处理(http://www.xuebuyuan.com/388108.html)。9+(3-1)*3+10/2对应的二叉树的构造过程如下图所示:
此二叉树做后序遍历就得到了后缀表达式。对应代码:http://www.davex.pw/2016/03/21/How-to-make-a-Expression-Tree/
3)还可以利用栈来实现中缀表达式转化为后缀表达式。转化方法如下所述:
a.从左向右扫描表达式,如果是数字就输出,否则转b。
b.如果当前扫描的字符是")",则栈顶元素出栈并输出一直到栈顶元素为"(",然后删除栈顶元素"(",并不输出。
c.如果扫描的字符或者栈顶元素是“(”,扫描的字符直接入栈。即使扫描的字符是")"也不会入栈,因为如果是")",会出栈至栈顶元素是"("。
d.如果扫描字符是"+"或者"-",则一直出栈至栈顶元素为"+"或者"-"或者"("。如果最终栈顶元素是"(",则扫描操作符直接入栈,否则,弹出栈顶元素,然后扫描的操作符再入栈。
e.如果扫描的操作符是"*"或者"/",如果栈顶元素是同优先级的"*"或者"/",首先将栈顶元素出栈,然后扫描的操作符入栈。否则,直接入栈。
f.扫描完成整个表达式之后,如果栈内还有元素,则依次全部出栈。
得到后缀表达式之后,从左至右扫描得到的后缀表达式,如果是数字,直接入栈,如果是运算符,则依次弹出两个栈顶元素,最先弹出的元素是右操作数,较后弹出的是左操作数,两操作数使用操作符计算结果结果再入栈,扫描结束时,栈中只有一个元素,就是计算结果。Java中double型变量直接进行加减乘除运算,会有精度损失,所以需要使用math包下的BigDecimal进行运算。
对应的代码如下,首先是栈的定义:
1 import java.math.BigDecimal; 2 import java.util.*; 3 4 /** 5 * Created by hfz on 2016/7/30. 6 */ 7 public class Stack_Array implements Stack { 8 public static int CAPACITY=40; 9 protected int capacity; 10 protected int top=-1; 11 protected Object[] S; 12 public Stack_Array(int capacity){ 13 this.capacity=capacity; 14 S=new Object[capacity]; 15 } 16 public Stack_Array(){ 17 this(CAPACITY);//一般情况下,使用this.方法名调用自身方法,不过在构造方法里可以使用this()的形式调用其他构造函数 18 } 19 public void push(Object ele) throws ExceptionStackFull { 20 if(getSize()==CAPACITY){ 21 throw new ExceptionStackFull("栈溢出!"); 22 } 23 S[++top]=ele; 24 } 25 public Object pop() throws ExceptionStackEmpty{ 26 if(isEmpty()){ 27 throw new ExceptionStackEmpty("栈为空,不能出栈!"); 28 } 29 Object ele=S[top]; 30 S[top--]=null; 31 return ele; 32 } 33 34 public Object top() throws ExceptionStackEmpty{ 35 if(isEmpty()){ 36 throw new ExceptionStackEmpty("栈为空,没有栈顶元素!"); 37 } 38 return S[top]; 39 } 40 public boolean isEmpty(){ 41 return (top<0); 42 } 43 public int getSize(){ 44 return top+1; 45 } 46 public static void main(String [] args){ 47 String s="9 + ( 3 - 1 ) * 3 + 10 / 2"; 48 String s1="20 * ( ( 2.44 - 1.8 ) / 0.4 + 0.15 )"; 49 50 System.out.println(reversePlishNotation2(s)); 51 System.out.println(reversePlishNotation2(s1)); 52 53 }
stack是接口,定义如下:
1 import java.util.Objects; 2 3 /** 4 * Created by hfz on 2016/7/30. 5 * 栈的基本功能有两个: 6 * 入栈:push(X) 7 * 出栈:pop() 8 * 还可以增加其他的功能: 9 * 获得栈顶元素:top() 10 * 判断栈是否为空:isEmpty() 11 * 获得栈中元素个数:getSize() 12 * 13 * 14 * 15 */ 16 public interface Stack { 17 void push(Object ele); 18 Object pop() throws ExceptionStackEmpty;//删除栈顶元素并返回 19 Object top() throws ExceptionStackEmpty;//返回栈顶元素,不删除。 20 boolean isEmpty(); 21 int getSize(); 22 } 23 class ExceptionStackEmpty extends RuntimeException{ 24 public ExceptionStackEmpty(String err){ 25 super(err);//调用父类只有一个String参数的构造函数 26 } 27 }
然后是reversePlishNotation2转换方法,首先利用栈将中缀表达式转化为后缀表达式,然后利用栈计算后缀表达式的值,表达式输入格式为字符串,操作数和运算符以及小括号以空格分隔:
1 /* 2 中缀表达式以#作为结束标志,以空格分割各个元素。 3 中缀表达式转化为后缀表达式(逆波兰表达式)的方法 4 1.从左向右扫描中缀表达式,遇到数字就直接输出。遇到运算符,如果其优先级高于栈顶元素或栈为空,入栈。如果低于栈顶 5 元素优先级,则栈顶元素出栈输出,一直到栈顶元素的优先级低于或等于此运算符。如果两个运算符优先级相同的话,弹出栈顶元素,然后再将运算符入栈。 6 7 2.对于小括号做另外处理,如果是左括号“(”,直接入栈。右括号")"的话,栈顶元素出栈输出,一直到栈顶元素为“(”,将“(”出栈 8 ,不过并不输出。右括号“)”并不入栈,直接丢弃。 9 3.扫描完成之后,如果栈中还有元素,依次输出。 10 */ 11 12 13 14 public static String reversePlishNotation2(String str){ 15 Stack_Array notationStack = new Stack_Array(); 16 String[] expressionString=str.split(" "); 17 int expressionLength = expressionString.length; 18 String currentString = "0"; 19 StringBuilder sb = new StringBuilder(); 20 Set<String> notationSet=new HashSet<>(); 21 notationSet.add("+"); 22 notationSet.add("-"); 23 notationSet.add("*"); 24 notationSet.add("/"); 25 notationSet.add("("); 26 notationSet.add(")"); 27 String topElement="null"; 28 for (int i = 0; i <expressionLength ; i++) { 29 currentString=expressionString[i]; 30 if(!notationSet.contains(currentString)){ 31 sb.append(currentString).append(" "); 32 } 33 else{//不是数字,而是操作符 34 try{ 35 topElement=(String)notationStack.top(); 36 } 37 catch (ExceptionStackEmpty ex){ 38 notationStack.push(currentString); 39 continue; 40 } 41 if(currentString.equals(")")){//当前扫描的字符是")",则操作符出栈至栈顶元素是"(",同时添加操作符,最后删除"("。 42 while(!topElement.equals("(")) { 43 sb.append(topElement).append(" "); 44 try { 45 notationStack.pop(); 46 topElement = (String) notationStack.top(); 47 } 48 catch (ExceptionStackEmpty ex){ 49 break; 50 } 51 } 52 if(topElement.equals("(")){ 53 notationStack.pop(); 54 } 55 } 56 else if(currentString.equals("(")||topElement.equals("(")){//如果扫描字符或者栈顶字符是"(",则扫描字符直接入栈。 57 // 即使扫描字符是")"也不会入栈,因为上面一层的判断条件,要求出栈至"("。 58 notationStack.push(currentString); 59 } 60 else if(currentString.equals("+")||currentString.equals("-")){ 61 //如果扫描字符是"+"或者"-",则一直出栈至栈顶元素为"+"或者"-"或者"("。如果最终栈顶元素是"(",则扫描操作符直接入栈,否则,弹出栈顶 62 //元素,然后扫描的操作符再入栈。 63 while (!topElement.equals("+")&&!topElement.equals("-")&&!topElement.equals("(")){ 64 sb.append(topElement).append(" "); 65 try{ 66 notationStack.pop(); 67 topElement=(String)notationStack.top(); 68 } 69 catch (ExceptionStackEmpty ex){ 70 break; 71 } 72 } 73 if(topElement.equals("+")||topElement.equals("-")){ 74 sb.append((String)notationStack.pop()).append(" "); 75 } 76 notationStack.push(currentString); 77 } 78 else if(currentString.equals("*")||currentString.equals("/")){ 79 //如果扫描的操作符是"*"或者"/",如果栈顶元素是同优先级的"*"或者"/",首先将栈顶元素出栈,然后扫描的操作符入栈。否则,直接入栈。 80 if(topElement.equals("*")||topElement.equals("/")){ 81 sb.append((String) notationStack.pop()).append(" "); 82 } 83 notationStack.push(currentString); 84 } 85 } 86 } 87 //扫描完成整个表达式之后,如果栈内还有元素,则依次全部出栈。 88 while (!notationStack.isEmpty()){ 89 sb.append(notationStack.pop()).append(" "); 90 } 91 //已经得到后缀表达式。 92 93 /* 94 从左至右扫描得到的后缀表达式,如果是数字,直接入栈,如果是运算符,则依次弹出两个栈顶元素,最先弹出的元素是右操作数,较后弹出的是左操作数,两操作数使用操作符计算结果 95 结果再入栈,扫描结束时,栈中只有一个元素,就是计算结果。 96 Java中double型变量直接进行加减乘除运算,会有精度损失,所以需要使用math包下的BigDecimal进行运算。 97 */ 98 99 String[] plishNotation=sb.toString().split(" "); 100 Stack_Array plishExpressionStack=new Stack_Array(); 101 BigDecimal leftValue,rightValue;//Java中double型变量直接进行加减乘除运算,会有精度损失,所以需要使用math包下的BigDecimal进行运算。 102 String resultValue="null"; 103 for(String str1:plishNotation){ 104 if(notationSet.contains(str1)){ 105 rightValue=new BigDecimal((String)plishExpressionStack.pop()); 106 leftValue=new BigDecimal((String)plishExpressionStack.pop()); 107 switch (str1){ 108 case "+": 109 resultValue=leftValue.add(rightValue).toString(); 110 break;//break结束switch语句,直接跳至switch后的下句语句。 111 case "-": 112 resultValue=leftValue.subtract(rightValue).toString(); 113 break; 114 case "*": 115 resultValue=leftValue.multiply(rightValue).toString(); 116 break; 117 case "/": 118 resultValue=leftValue.divide(rightValue).toString(); 119 break; 120 } 121 plishExpressionStack.push(resultValue); 122 } 123 else{ 124 plishExpressionStack.push(str1); 125 } 126 } 127 return (String)plishExpressionStack.pop(); 128 }
4)调度场算法(http://hczhcz.github.io/2014/03/07/shunting-yard-algorithm-3.html)与上述思路类似,只是不再转化为后缀表达式,实际上转化为后缀表达式的过程就用了调度厂的思想。使用调度厂算法求解算数表达式的值时,定义两个栈,分别是数字栈和符号栈。从左向右扫描算数表达式,如果扫描到数字,就将其存入数字栈。否则,就存入符号栈。每次符号栈有元素出栈时(“(”出栈除外),数字栈也出栈两个元素,先出栈的元素为右操作数,后出栈的元素为左操作数,两者结合运算符运算之后,将结果存入数字栈。最终,表达式扫描完成之后,如果符号栈不为空,则出栈,数字栈也同时出栈两个元素,直至符号栈为空。
代码如下,部分重复代码可以改写成函数:
1 public static String shuntingYardAlgorithm(String str){ 2 Stack_Array notationStack = new Stack_Array(); 3 Stack_Array numberStack=new Stack_Array(); 4 BigDecimal leftValue,rightValue;//Java中double型变量直接进行加减乘除运算,会有精度损失,所以需要使用math包下的BigDecimal进行运算。 5 String resultValue="null"; 6 String[] expressionString=str.split(" "); 7 int expressionLength = expressionString.length; 8 String currentString = "0"; 9 StringBuilder sb = new StringBuilder(); 10 Set<String> notationSet=new HashSet<>(); 11 notationSet.add("+"); 12 notationSet.add("-"); 13 notationSet.add("*"); 14 notationSet.add("/"); 15 notationSet.add("("); 16 notationSet.add(")"); 17 String topElement="null"; 18 for (int i = 0; i <expressionLength ; i++) { 19 currentString=expressionString[i]; 20 if(!notationSet.contains(currentString)){ 21 //sb.append(currentString).append(" "); 22 numberStack.push(currentString); 23 } 24 else{//不是数字,而是操作符 25 try{ 26 topElement=(String)notationStack.top(); 27 } 28 catch (ExceptionStackEmpty ex){ 29 notationStack.push(currentString); 30 continue; 31 } 32 if(currentString.equals(")")){//当前扫描的字符是")",则操作符出栈至栈顶元素是"(",同时添加操作符,最后删除"("。 33 34 //字符栈出栈时,数字栈同时也弹出两个元素,将运算结果存入数字栈。 35 36 37 while(!topElement.equals("(")) { 38 rightValue=new BigDecimal((String)numberStack.pop()); 39 leftValue=new BigDecimal((String)numberStack.pop()); 40 switch (topElement){ 41 case "+": 42 resultValue=leftValue.add(rightValue).toString(); 43 break;//break结束switch语句,直接跳至switch后的下句语句。 44 case "-": 45 resultValue=leftValue.subtract(rightValue).toString(); 46 break; 47 case "*": 48 resultValue=leftValue.multiply(rightValue).toString(); 49 break; 50 case "/": 51 resultValue=leftValue.divide(rightValue).toString(); 52 break; 53 } 54 numberStack.push(resultValue); 55 try { 56 notationStack.pop(); 57 topElement = (String) notationStack.top(); 58 } 59 catch (ExceptionStackEmpty ex){ 60 break; 61 } 62 } 63 if(topElement.equals("(")){ 64 notationStack.pop(); 65 } 66 } 67 else if(currentString.equals("(")||topElement.equals("(")){//如果扫描字符或者栈顶字符是"(",则扫描字符直接入栈。 68 // 即使扫描字符是")"也不会入栈,因为上面一层的判断条件,要求出栈至"("。 69 notationStack.push(currentString); 70 } 71 else if(currentString.equals("+")||currentString.equals("-")){ 72 //如果扫描字符是"+"或者"-",则一直出栈至栈顶元素为"+"或者"-"或者"("。如果最终栈顶元素是"(",则扫描操作符直接入栈,否则,弹出栈顶 73 //元素,然后扫描的操作符再入栈。 74 75 //字符栈出栈时,数字栈同时也弹出两个元素,将运算结果存入数字栈。 76 77 while (!topElement.equals("+")&&!topElement.equals("-")&&!topElement.equals("(")){ 78 try{ 79 if( ((String)notationStack.pop()).equals("*")){ 80 rightValue=new BigDecimal((String)numberStack.pop()); 81 leftValue=new BigDecimal((String)numberStack.pop()); 82 resultValue=leftValue.multiply(rightValue).toString(); 83 } 84 else{ 85 rightValue=new BigDecimal((String)numberStack.pop()); 86 leftValue=new BigDecimal((String)numberStack.pop()); 87 resultValue=leftValue.divide(rightValue).toString(); 88 } 89 numberStack.push(resultValue); 90 topElement=(String)notationStack.top(); 91 } 92 catch (ExceptionStackEmpty ex){ 93 break; 94 } 95 } 96 if(topElement.equals("+")||topElement.equals("-")){ 97 98 //字符栈出栈时,数字栈同时也弹出两个元素,将运算结果存入数字栈。 99 100 if( ((String)notationStack.pop()).equals("+")){ 101 rightValue=new BigDecimal((String)numberStack.pop()); 102 leftValue=new BigDecimal((String)numberStack.pop()); 103 resultValue=leftValue.add(rightValue).toString(); 104 105 } 106 else{ 107 rightValue=new BigDecimal((String)numberStack.pop()); 108 leftValue=new BigDecimal((String)numberStack.pop()); 109 resultValue=leftValue.subtract(rightValue).toString(); 110 } 111 numberStack.push(resultValue); 112 } 113 notationStack.push(currentString); 114 } 115 else if(currentString.equals("*")||currentString.equals("/")){ 116 //如果扫描的操作符是"*"或者"/",如果栈顶元素是同优先级的"*"或者"/",首先将栈顶元素出栈,然后扫描的操作符入栈。否则,直接入栈。 117 118 //字符栈出栈时,数字栈同时也弹出两个元素,将运算结果存入数字栈。 119 120 if(topElement.equals("*")||topElement.equals("/")){ 121 if( ((String)notationStack.pop()).equals("*")){ 122 rightValue=new BigDecimal((String)numberStack.pop()); 123 leftValue=new BigDecimal((String)numberStack.pop()); 124 resultValue=leftValue.multiply(rightValue).toString(); 125 126 } 127 else{ 128 rightValue=new BigDecimal((String)numberStack.pop()); 129 leftValue=new BigDecimal((String)numberStack.pop()); 130 resultValue=leftValue.divide(rightValue).toString(); 131 } 132 numberStack.push(resultValue); 133 } 134 notationStack.push(currentString); 135 } 136 } 137 } 138 139 //最终,表达式扫描完成之后,如果符号栈不为空,则出栈,数字栈也同时出栈两个元素,直至符号栈为空。 140 141 while(!notationStack.isEmpty()){ 142 topElement=(String)notationStack.pop(); 143 rightValue=new BigDecimal((String)numberStack.pop()); 144 leftValue=new BigDecimal((String)numberStack.pop()); 145 switch (topElement){ 146 case "+": 147 resultValue=leftValue.add(rightValue).toString(); 148 break;//break结束switch语句,直接跳至switch后的下句语句。 149 case "-": 150 resultValue=leftValue.subtract(rightValue).toString(); 151 break; 152 case "*": 153 resultValue=leftValue.multiply(rightValue).toString(); 154 break; 155 case "/": 156 resultValue=leftValue.divide(rightValue).toString(); 157 break; 158 } 159 numberStack.push(resultValue); 160 } 161 return (String)numberStack.pop(); 162 }