根据表达式序列(前缀、中缀、后缀)构建表达式树
如果输入序列是表达式(前缀表达式、中缀表达式、后缀表达式,中缀表达式要求带括号有几个运算符就带几个)则构建出来的树为表达式树,对该树前、中、后序遍历得到对应序的表达式。
不过,中缀表达式带括号,而表达式树不带括号,故中序遍历表达式树时需要加适当的括号才能得到正确的中缀表达式。
1、表达式树的构建与遍历
0、工具函数(链表节点定义、读取下一个字符、判断字符是否操作数):
1 typedef struct node 2 { 3 char data; 4 struct node * lchild; 5 struct node * rchild; 6 }BTNode,*BTREE; 7 8 9 char nextToken(char infix[])//括号、操作数、操作符等 都为一个字符 10 { 11 static int pos=0; 12 while(infix[pos]!='\0' && infix[pos]==' '){pos++;}//跳过空格 13 return infix[pos++]; 14 } 15 int isOpNum(char ch)//是否是操作数 16 { 17 if(ch=='#' || ch=='(' || ch==')' || ch=='+' || ch=='-' || ch=='*' || ch=='/' || ch==' ' || ch=='|' ) 18 { 19 return 0; 20 } 21 return 1; 22 }
1、输入前缀表达式构建表达式树(递归):
1 void createPrefix_recursive(char prefix[],BTREE &T) 2 {//递归方式_由前缀表达式构建表达式树 3 char x=nextToken(prefix); 4 5 T=(BTREE)malloc(sizeof(BTNode)); 6 T->data=x; 7 T->lchild=NULL; 8 T->rchild=NULL; 9 10 if(!isOpNum(x))//是操作符。前缀表达式的最后一个字符一定是操作数,所以下面的递归会停止。 11 { 12 createPrefix_recursive(prefix,T->lchild); 13 createPrefix_recursive(prefix,T->rchild); 14 } 15 }
2、输入中缀表达式构建表达式树(递归):
1 void createInfix_recursive(char infix[],BTREE &T) 2 {//递归方式_由中缀表达式构建表达式树,要求输入的中缀表达式加括号,有几个操作数就几个括号 。如果输入的前、中、后缀表达式都带括号,则很容易由此法改造得到构建表达式树的算法。 3 char x=nextToken(infix); 4 5 T=(BTREE)malloc(sizeof(BTNode)); 6 T->lchild=NULL; 7 T->rchild=NULL; 8 9 if(x=='(') 10 {//处理括号里的表达式 11 createInfix_recursive(infix,T->lchild);//表达式的左操作数 12 13 x=nextToken(infix);//表达式的操作符 14 T->data=x; 15 16 createInfix_recursive(infix,T->rchild);//表达式的右操作数 17 nextToken(infix);//右括号 18 } 19 else 20 { 21 T->data=x; 22 } 23 }
注:
要求输入的中缀表达式带括号,有n个运算符就有n个括号,不能多也不能少。为什么?因为加括号就是为了给运算符定优先级。
必须严格符合该括号的要求,否则构建的结果可能有误。比如 1+2*3-4 或 1+(2*3)-4,虽表达式自身没问题,但缺少了括号。即使要给表达式补全括号后用此法,也得先转前缀或后缀表达式,这就导致鸡生蛋蛋生鸡问题了...
括号数多于运算符数也不行,比如 ((( 1+2 ))-3)
若给不带括号的中缀表达式加括号有几种?卡特兰数种,因为n个运算符入栈有卡特兰数种出栈顺序,相关题:LeetCode241
3、输入后缀表达式构建表达式树(非递归)
1 #define M 100 2 void createPostfix_nonrecursive(char postfix[],BTREE &T) 3 {//非递归方式_由后缀表达式构建表达式树 4 BTREE stack[M],p; 5 int top=-1; 6 char x; 7 while(1) 8 { 9 x=nextToken(postfix); 10 if(x=='\0') return; 11 12 p=(BTREE)malloc(sizeof(BTNode)) ; 13 p->data=x; 14 p->lchild=NULL; 15 p->rchild=NULL; 16 17 if(isOpNum(x)) 18 {//操作数 19 stack[++top]=p; 20 } 21 else 22 {//操作符 23 p->lchild=stack[top-1]; 24 p->rchild=stack[top]; 25 stack[top-1]=p; 26 top--; 27 T=p; 28 } 29 } 30 T=stack[0]; 31 }
4、表达式树遍历(递归,前缀、中缀、后缀):(中缀遍历在遍历时需要加括号才能得到正确结果,方法为访问到内部节点时加左右括号。)
1 void searchPrefix(BTREE T) 2 { 3 if(T!=NULL) 4 {//·ÃÎʵ½ÄÚ²¿½Úµãʱ×óÓÒ¼ÓÀ¨ºÅ 5 // if(T->lchild!=NULL && T->rchild!=NULL){ printf("("); } 6 7 printf("%c",T->data); 8 searchPrefix(T->lchild); 9 searchPrefix(T->rchild); 10 11 // if(T->lchild!=NULL && T->rchild!=NULL){ printf(")"); } 12 } 13 } 14 void searchInfix(BTREE T) 15 { 16 if(T!=NULL) 17 {//ÖÐÐò±éÀú±í´ïʽÊ÷ÐèÒªÌí¼ÓÀ¨ºÅÐÅÏ¢£¬ÒÔ±£Ö¤ÔËËãÓÅÏȼ¶£º·ÃÎʵ½ÄÚ²¿½Úµãʱ×óÓÒ¼ÓÀ¨ºÅ 18 if(T->lchild!=NULL && T->rchild!=NULL){ printf("("); } 19 20 searchInfix(T->lchild); 21 printf("%c",T->data); 22 searchInfix(T->rchild); 23 24 if(T->lchild!=NULL && T->rchild!=NULL){ printf(")"); } 25 } 26 } 27 void searchPostfix(BTREE T) 28 { 29 if(T!=NULL) 30 {//·ÃÎʵ½ÄÚ²¿½Úµãʱ×óÓÒ¼ÓÀ¨ºÅ 31 // if(T->lchild!=NULL && T->rchild!=NULL){ printf("("); } 32 33 searchPostfix(T->lchild); 34 searchPostfix(T->rchild); 35 printf("%c",T->data); 36 37 // if(T->lchild!=NULL && T->rchild!=NULL){ printf(")"); } 38 } 39 }
如果遍历前缀、后缀表达式时也要加括号,则方法一样,在访问到内部节点时加左右括号即可。从这里我们受到启发,不借助程序,手动将中缀表达式转换为前后缀表达式的方法:把括号内的操作符放到括号前面或后面然后去掉括号即可;反之,要将前后缀表达式转为中缀表达式,只要加上括号然后把操作符放到括号内两操作数中间即可!
5、测试:
1 int main() 2 { 3 // *+A/-BCDE 4 // ((A+((B-C)/D))*E) 5 // ABC-D/+E* 6 char str[]="ABC-D/+E*"; 7 BTREE T; 8 9 // createPrefix_recursive(str,T); 10 // createInfix_recursive(str,T); 11 createPostfix_nonrecursive(str,T); 12 13 14 searchPrefix(T); 15 printf("\n"); 16 17 searchInfix(T); 18 printf("\n"); 19 20 searchPostfix(T); 21 printf("\n"); 22 23 return 0; 24 }
2、前中后缀表达式间的转换(以中缀表达式转后缀表达式为例)
// *+A/-BCDE
// ((A+((B-C)/D))*E)
// ABC-D/+E*
2.1、转换方法
手动转换:把括号内的操作符放到括号前面或后面然后去掉括号即可;反之,要将前后缀表达式转为中缀表达式,只要加上括号然后把操作符放到括号内两操作数中间即可!
程序转换:
法1:由中缀表达式构建表达式树,然后后序遍历该表达式树即可。有括号齐全的要求,实际输入比较难符合该要求。故通常用下面的法2。
法2:不借助表达式树,通过定义操作符的优先级纯借助栈来实现。此法更通用,没有上述括号要求。如下:
1 #include<stdio.h> 2 char nextToken(char infix[])//括号、操作数、操作符等 都为一个字符 3 { 4 static int pos=0; 5 while(infix[pos]!='\0' && infix[pos]==' '){pos++;}//跳过空格 6 return infix[pos++]; 7 } 8 int isOpNum(char ch)//是否是操作数 9 { 10 if(ch=='#' || ch=='(' || ch==')' || ch=='+' || ch=='-' || ch=='*' || ch=='/' || ch==' ') 11 { 12 return 0; 13 } 14 return 1; 15 } 16 int isCurchPriorToChtop(char chCur,char chTop)//当前元素是否比栈顶元素优先级大 17 { 18 if(chCur=='(' || chTop=='#' || chTop=='(') return 1; 19 else if( (chCur=='*'||chCur=='/') && chTop!='*' && chTop!='/' ) return 1; 20 else return 0; 21 } 22 23 //输入中缀表达式转后缀,单纯用堆栈。假定中缀表达式符合语法。操作数或操作符都为一个char,可能有空格 24 //也可通过对输入的中缀表达式构建表达式树然后后序遍历来得到后缀表达式。 25 #define M 100 26 void infix2Postfix(char infix[]) 27 { 28 char stack[M],x; 29 int top=-1; 30 stack[++top]='#'; 31 while(1) 32 { 33 x=nextToken(infix); 34 if(isOpNum(x)) 35 { 36 printf("%c",x); 37 } 38 else if(x=='#') 39 {//中缀表达式扫描结束 40 while(top>=0) 41 { 42 printf("%c",stack[top--]); 43 } 44 return; 45 } 46 else if(x==')') 47 { 48 while(stack[top]!='(') 49 { 50 printf("%c",stack[top--]); 51 } 52 top--; 53 } 54 else 55 { 56 while(!isCurchPriorToChtop(x,stack[top])) 57 { 58 printf("%c",stack[top--]); 59 } 60 stack[++top]=x; 61 } 62 } 63 } 64 int main() 65 { 66 char infix[]="(((A+(B-C)/D) *E)) #"; 67 int n=sizeof(infix)/sizeof(char); 68 infix2Postfix(infix); 69 return 0; 70 }
给中缀表达式补全括号的方法(不能改变中缀表达式的计算优先级):
转后缀表达式后按后缀表达式的“计算”处理流程处理,只不过此时“计算”不是算式运算而是给两个数中间加操作符、头尾加括号。或者转前缀也可,处理类似。
2.2、转换相关的性质
中缀表达式转前后缀表达式后,操作数顺序不变、操作符顺序会改变,但前后缀表达式的操作符顺序恰好相反。
2.3、前后缀表达式的计算
前缀表达式:从后往前扫,遇到操作数入栈、遇到字符时取两栈顶元素进行相应运算后结果入栈。
后缀表达式:与上类似,只是是从前往后扫。
相关阅读:二叉树-MarchOn
应用例子:
20230611 广发证券笔试题——Java
1 给定后缀表达式计算其值
public class GuangFa1 { static class Solution { private boolean isOperator(String token) { return token.equals("+") || token.equals("-") || token.equals("*") || token.equals("/"); } /* Write Code Here */ public int calculate(String[] tokens) { Stack<Integer> stack = new Stack<>(); for (int i = 0; i < tokens.length; i++) { String token = tokens[i]; if (isOperator(token)) { int stackLen = stack.size(); int opNum1 = stack.get(stackLen - 2); int opNum2 = stack.get(stackLen - 1); stack.pop(); stack.pop(); int res = 0; if (token.equals("+")) { res = opNum1 + opNum2; } else if (token.equals("-")) { res = opNum1 - opNum2; } else if (token.equals("*")) { res = opNum1 * opNum2; } else if (token.equals("/")) { res = opNum1 / opNum2; } stack.push(res); } else { stack.push(Integer.parseInt(token)); } } return stack.pop(); } } public static void main(String[] args) { Scanner in = new Scanner(System.in); int res; int tokens_size = 0; tokens_size = in.nextInt(); if (in.hasNextLine()) in.nextLine(); String[] tokens = new String[tokens_size]; String tokens_item; for (int tokens_i = 0; tokens_i < tokens_size; tokens_i++) { try { tokens_item = in.nextLine(); } catch (Exception e) { tokens_item = null; } tokens[tokens_i] = tokens_item; } res = new Solution().calculate(tokens); System.out.println(String.valueOf(res)); } }
2 给定中缀表达式求后缀表达式
(只含有必须的括号,如 (1+2)/3)
static class Solution { // 是否是操作数 private boolean isDigit(char ch) { return '0' <= ch && ch <= '9'; } // 寻找 next token private int infixStrPos = 0; private String nextToken(String infixStr) { if (null != infixStr && infixStrPos < infixStr.length()) { char ch = infixStr.charAt(infixStrPos); if (isDigit(ch)) { int end = infixStrPos + 1; while (end < infixStr.length() && isDigit(infixStr.charAt(end))) { end++; } String res = infixStr.substring(infixStrPos, end); infixStrPos = end; return res; } else { infixStrPos++; return ch + ""; } } return null; } // 判断第一个字符是否比第二个优先级高 private boolean isPriorTo(char ch1, char ch2) { if (ch1 == '(' || ch2 == '(') return true; else if ((ch1 == '*' || ch1 == '/') && !(ch2 == '*' || ch2 == '/')) return true; else return false; } private boolean isOperatorNum(String token) { return !(token.equals("+") || token.equals("-") || token.equals("*") || token.equals("/") || token.equals("(") || token.equals(")")); } // 中缀转后缀。例如 1+((2+3-0)*4)-5 -> 123+0-4*+5- public String NifixToRPN(String nifix) { nifix = nifix.replaceAll(" ", ""); Stack<String > operatorStack = new Stack<>();// 操作数栈 StringBuilder res = new StringBuilder(); while (true) { String token = nextToken(nifix); if (null == token) {// 结束 while (!operatorStack.isEmpty()) { res.append(operatorStack.pop()); } return res.toString(); } else if (isOperatorNum(token)) { res.append(token); } else if (token.equals(")")) { while (!operatorStack.peek().equals("(")) { res.append(operatorStack.pop()); } operatorStack.pop();//去除"(" } else { while (!operatorStack.isEmpty() && !isPriorTo(token.charAt(0), operatorStack.peek().charAt(0))) { res.append(operatorStack.pop()); } operatorStack.push(token); } } } public static void main(String[] args) { Scanner in = new Scanner(System.in); String res; String nifix; try { nifix = in.nextLine(); } catch (Exception e) { nifix = null; } res = new Solution().NifixToRPN(nifix); System.out.println(res); } }
若括号齐全,则可用构造表达式树然后后序遍历的方法:
public class GuangFa2 { static class Node { String val; Node left; Node right; } // 通过构建表达式树,然后后序遍历得到。该法要求输入的中缀表达式有多少个四则运算操作符就得有多少个括号 static class Solution2 { // 是否是操作数 private boolean isDigit(char ch) { return !(ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == '(' || ch == ')'); } // 寻找 next token private int infixStrPos = 0; private String nextToken(String infixStr) { if (null != infixStr && infixStrPos < infixStr.length()) { char ch = infixStr.charAt(infixStrPos); if (isDigit(ch)) { int end = infixStrPos + 1; while (end < infixStr.length() && isDigit(infixStr.charAt(end))) { end++; } String res = infixStr.substring(infixStrPos, end); infixStrPos = end; return res; } else { infixStrPos++; return ch + ""; } } return null; } //由中缀表达式构建表达式树。要求输入的中缀表达式有多少个四则运算操作符就得有多少个括号 private Node constructInfixTree(String infixStr) { String token = nextToken(infixStr); if (null == token) return null; Node root = new Node(); if (token.equals("(")) { root.left = constructInfixTree(infixStr); root.val = nextToken(infixStr); root.right = constructInfixTree(infixStr); nextToken(infixStr);//右括号 } else { root.val = token; } return root; } //后序遍历 List<String> postOrderTokens = new ArrayList<>(); private void postOrder(Node root) { if (root != null) { postOrder(root.left); postOrder(root.right); postOrderTokens.add(root.val); } } //由中缀表达式构建表达式树,然后对该树后序遍历。要求中缀表达式括号齐全,有多少个运算符就有多少个括号 /* Write Code Here */ public String NifixToRPN(String nifix) { nifix = nifix.replaceAll(" ", ""); //在首尾加上括号 // nifix = "(" + nifix + ")"; // 构建中缀树 Node infixTree = constructInfixTree(nifix); //后序遍历 postOrder(infixTree); return postOrderTokens.stream().collect(Collectors.joining(",")); } } // 中缀转后缀表达式 }