Java数据结构和算法(六)——前缀、中缀、后缀表达式
Java数据结构和算法(六)——前缀、中缀、后缀表达式
前面我们介绍了三种数据结构,第一种数组主要用作数据存储,但是后面的两种栈和队列我们说主要作为程序功能实现的辅助工具,其中在介绍栈时我们知道栈可以用来做单词逆序,匹配关键字符等等,那它还有别的什么功能吗?以及数据结构与本篇博客的主题前缀、中缀、后缀表达式有什么关系呢?
1、人如何解析算术表达式
如何解析算术表达式?或者换种说法,遇到某个算术表达式,我们是如何计算的:
①、求值 3+4-5
这个表达式,我们在看到3+4后都不能直接计算3+4的值,知道看到4后面的 - 号,因为减号的优先级和前面的加号一样,所以可以计算3+4的值了,如果4后面是 * 或者 /,那么就要在乘除过后才能做加法操作,比如:
②、求值 3+4*5
这个不能先求3+4的值,因为4后面的*运算级别比前面的+高。通过这两个表达式的说明,我们可以总结解析表达式的时候遵循的几条规则:
①、从左到右读取算式。
②、已经读到了可以计算值的两个操作数和一个操作符时,可以计算,并用计算结果代替那两个操作数和一个操作符。
③、继续这个过程,从左到右,能算就算,直到表达式的结尾。
2、计算机如何解析算术表达式
对于前面的表达式 3+4-5,我们人是有思维能力的,能根据操作符的位置,以及操作符的优先级别能算出该表达式的结果。但是计算机怎么算?
计算机必须要向前(从左到右)来读取操作数和操作符,等到读取足够的信息来执行一个运算时,找到两个操作数和一个操作符进行运算,有时候如果后面是更高级别的操作符或者括号时,就必须推迟运算,必须要解析到后面级别高的运算,然后回头来执行前面的运算。我们发现这个过程是极其繁琐的,而计算机是一个机器,只认识高低电平,想要完成一个简单表达式的计算,我们可能要设计出很复杂的逻辑电路来控制计算过程,那更不用说很复杂的算术表达式,所以这样来解析算术表达式是不合理的,那么我们应该采取什么办法呢?
请大家先看看什么是前缀表达式,中缀表达式,后缀表达式:这三种表达式其实就是算术表达式的三种写法,以 3+4-5为例
①、前缀表达式:操作符在操作数的前面,比如 +-543
②、中缀表达式:操作符在操作数的中间,这也是人类最容易识别的算术表达式 3+4-5
③、后缀表达式:操作符在操作数的后面,比如 34+5-
上面我们讲的人是如何解析算术表达式的,也就是解析中缀表达式,这是人最容易识别的,但是计算机不容易识别,计算机容易识别的是前缀表达式和后缀表达式,将中缀表达式转换为前缀表达式或者后缀表达式之后,计算机能很快计算出表达式的值,那么中缀表达式是如何转换为前缀表达式和后缀表达式,以及计算机是如何解析前缀表达式和后缀表达式来得到结果的呢?
3、后缀表达式
后缀表达式,指的是不包含括号,运算符放在两个运算对象的后面,所有的计算按运算符出现的顺序,严格从左向右进行(不再考虑运算符的优先规则)。
由于后缀表达式的运算符在两个操作数的后面,那么计算机在解析后缀表达式的时候,只需要从左向右扫描,也就是只需要向前扫描,而不用回头扫描,遇到运算符就将运算符放在前面两个操作符的中间(这里先不考虑乘方类似的单目运算),一直运算到最右边的运算符,那么就得出运算结果了。既然后缀表达式这么好,那么问题来了:
①、如何将中缀表达式转换为后缀表达式?
对于这个问题,转换的规则如下:
一、先自定义一个栈
1 package com.ys.poland; 2 3 public class MyCharStack { 4 private char[] array; 5 private int maxSize; 6 private int top; 7 8 public MyCharStack(int size){ 9 this.maxSize = size; 10 array = new char[size]; 11 top = -1; 12 } 13 14 //压入数据 15 public void push(char value){ 16 if(top < maxSize-1){ 17 array[++top] = value; 18 } 19 } 20 21 //弹出栈顶数据 22 public char pop(){ 23 return array[top--]; 24 } 25 26 //访问栈顶数据 27 public char peek(){ 28 return array[top]; 29 } 30 31 //查看指定位置的元素 32 public char peekN(int n){ 33 return array[n]; 34 } 35 36 //为了便于后面分解展示栈中的内容,我们增加了一个遍历栈的方法(实际上栈只能访问栈顶元素的) 37 public void displayStack(){ 38 System.out.print("Stack(bottom-->top):"); 39 for(int i = 0 ; i < top+1; i++){ 40 System.out.print(peekN(i)); 41 System.out.print(' '); 42 } 43 System.out.println(""); 44 } 45 46 //判断栈是否为空 47 public boolean isEmpty(){ 48 return (top == -1); 49 } 50 51 //判断栈是否满了 52 public boolean isFull(){ 53 return (top == maxSize-1); 54 } 55 56 }
二、前缀表达式转换为后缀表达式
1 package com.ys.poland; 2 3 public class InfixToSuffix { 4 private MyCharStack s1;//定义运算符栈 5 private MyCharStack s2;//定义存储结果栈 6 private String input; 7 8 //默认构造方法,参数为输入的中缀表达式 9 public InfixToSuffix(String in){ 10 input = in; 11 s1 = new MyCharStack(input.length()); 12 s2 = new MyCharStack(input.length()); 13 } 14 //中缀表达式转换为后缀表达式,将结果存储在栈中返回,逆序显示即后缀表达式 15 public MyCharStack doTrans(){ 16 for(int j = 0 ; j < input.length() ; j++){ 17 System.out.print("s1栈元素为:"); 18 s1.displayStack(); 19 System.out.print("s2栈元素为:"); 20 s2.displayStack(); 21 char ch = input.charAt(j); 22 System.out.println("当前解析的字符:"+ch); 23 switch (ch) { 24 case '+': 25 case '-': 26 gotOper(ch,1); 27 break; 28 case '*': 29 case '/': 30 gotOper(ch,2); 31 break; 32 case '(': 33 s1.push(ch);//如果当前字符是'(',则将其入栈 34 break; 35 case ')': 36 gotParen(ch); 37 break; 38 default: 39 //1、如果当前解析的字符是操作数,则直接压入s2 40 //2、 41 s2.push(ch); 42 break; 43 }//end switch 44 }//end for 45 46 while(!s1.isEmpty()){ 47 s2.push(s1.pop()); 48 } 49 return s2; 50 } 51 52 public void gotOper(char opThis,int prec1){ 53 while(!s1.isEmpty()){ 54 char opTop = s1.pop(); 55 if(opTop == '('){//如果栈顶是'(',直接将操作符压入s1 56 s1.push(opTop); 57 break; 58 }else{ 59 int prec2; 60 if(opTop == '+' || opTop == '-'){ 61 prec2 = 1; 62 }else{ 63 prec2 = 2; 64 } 65 if(prec2 < prec1){//如果当前运算符比s1栈顶运算符优先级高,则将运算符压入s1 66 s1.push(opTop); 67 break; 68 }else{//如果当前运算符与栈顶运算符相同或者小于优先级别,那么将S1栈顶的运算符弹出并压入到S2中 69 //并且要再次再次转到while循环中与 s1 中新的栈顶运算符相比较; 70 s2.push(opTop); 71 } 72 } 73 74 }//end while 75 //如果s1为空,则直接将当前解析的运算符压入s1 76 s1.push(opThis); 77 } 78 79 //当前字符是 ')' 时,如果栈顶是'(',则将这一对括号丢弃,否则依次弹出s1栈顶的字符,压入s2,直到遇到'(' 80 public void gotParen(char ch){ 81 while(!s1.isEmpty()){ 82 char chx = s1.pop(); 83 if(chx == '('){ 84 break; 85 }else{ 86 s2.push(chx); 87 } 88 } 89 } 90 91 }
三、测试
1 @Test 2 public void testInfixToSuffix(){ 3 String input; 4 System.out.println("Enter infix:"); 5 Scanner scanner = new Scanner(System.in); 6 input = scanner.nextLine(); 7 InfixToSuffix in = new InfixToSuffix(input); 8 MyCharStack my = in.doTrans(); 9 my.displayStack(); 10 }
四、结果
五、分析
②、计算机如何实现后缀表达式的运算?
1 package com.ys.poland; 2 3 public class CalSuffix { 4 private MyIntStack stack; 5 private String input; 6 7 public CalSuffix(String input){ 8 this.input = input; 9 stack = new MyIntStack(input.length()); 10 11 } 12 13 public int doCalc(){ 14 int num1,num2,result; 15 for(int i = 0 ; i < input.length() ; i++){ 16 char c = input.charAt(i); 17 if(c >= '0' && c <= '9'){ 18 stack.push((int)(c-'0'));//如果是数字,直接压入栈中 19 }else{ 20 num2 = stack.pop();//注意先出来的为第二个操作数 21 num1 = stack.pop(); 22 switch (c) { 23 case '+': 24 result = num1+num2; 25 break; 26 case '-': 27 result = num1-num2; 28 break; 29 case '*': 30 result = num1*num2; 31 break; 32 case '/': 33 result = num1/num2; 34 break; 35 default: 36 result = 0; 37 break; 38 }//end switch 39 40 stack.push(result); 41 }//end else 42 }//end for 43 result = stack.pop(); 44 return result; 45 } 46 47 public static void main(String[] args) { 48 //中缀表达式:1*(2+3)-5/(2+3) = 4 49 //后缀表达式:123+*123+/- 50 CalSuffix cs = new CalSuffix("123+*523+/-"); 51 System.out.println(cs.doCalc()); //4 52 } 53 54 }
4、前缀表达式
前缀表达式,指的是不包含括号,运算符放在两个运算对象的前面,严格从右向左进行(不再考虑运算符的优先规则),所有的计算按运算符出现的顺序。
注意:后缀表达式是从左向右解析,而前缀表达式是从右向左解析。
①、如何将中缀表达式转换为前缀表达式?
②、计算机如何实现前缀表达式的运算?