表达式树
二叉树是表达式处理的常用工具,例如,a+b*(c-d)-e/f可以表示成如下所示的二叉树
其中,每个非叶子节点表示一个运算符,左子树是第一个运算数对应的表达式,右子树是第二个表达式对应的表达式。每个叶子节点都是数。
其在空间利用上也非常高效,节点数等于表达式的长度。
表达式转二叉树
lrj说方法有很多种,下面介绍他讲的一种:找到“最后计算”的运算符(它是整个表达式树的根),然后递归处理左右两边。
1 const int maxn = 1000 + 10; 2 char str[maxn]; 3 int lch[maxn + 1], rch[maxn + 1]; char op[maxn + 1]; //每个结点的左右子结点编号和字符
4 int nc = 0; //结点数
5 int build_tree(char* s, int x, int y) 6 { 7 int i, c1=-1, c2=-1, p=0; 8 int u; 9 if(y-x == 1) //仅一个字符,建立单独结点
10 { 11 u = ++nc; 12 lch[u] = rch[u] = 0; 13 op[u] = s[x]; 14 return u; 15 } 16
17 for (i = x; i < y; i++) //寻找根节点的位置
18 { 19 switch (s[i]) 20 { 21 case '(': p++; break; 22 case ')': p--; break; 23 case '+': 24 case '-': if (!p) c1 = i; break; 25 case '*': 26 case '/': if (!p) c2 = i; break; 27 } 28 } 29 if (c1 < 0) c1 = c2; //找不到括号外的加减号,就用乘除号
30 if(c1 < 0) return build_tree(s, x+1, y-1); //整个表达式被一对括号括起来
31 u = ++nc; 32 lch[u] = build_tree(s, x, c1); 33 rch[u] = build_tree(s, c1+1, y); 34 op[u] = s[c1]; 35 return u; 36 }
前缀式、中缀式、后缀式
前缀表达式和后缀表达式分别对应表达式树前序和后序遍历的结果,如果不考虑括号,中缀表达式对应表达式树中序遍历的结果。
1 //中序遍历
2 void InOrder(int root) 3 { 4 if (lch[root] > 0) InOrder(lch[root]); 5 printf("%c ", op[root]); 6 if (rch[root] > 0) InOrder(rch[root]); 7 } 8
9 //后序遍历
10 void PostOrder(int root) 11 { 12 if (lch[root] > 0) PostOrder(lch[root]); 13 if (rch[root] > 0) PostOrder(rch[root]); 14 printf("%c ", op[root]); 15 }
表达式求值
有了表达式树求值就非常简单了,先求左子树的值,再求右子树值,然后根据根节点的运算符计算最终的值。整个过程采用递归求解。
(注意!!这种方法只能求个位数表达式的值)
1 //根据表达式二叉树求值
2 int cal(int root) 3 { 4 int ans = 0; 5 char ch = op[root]; 6 if (isdigit(ch)) return ch - '0'; 7 switch (ch) 8 { 9 case '+': ans = cal(lch[root]) + cal(rch[root]); break; 10 case '-': ans = cal(lch[root]) - cal(rch[root]); break; 11 case '*': ans = cal(lch[root]) * cal(rch[root]); break; 12 case '/': ans = cal(lch[root]) / cal(rch[root]); break; 13 } 14 return ans; 15 }
打印二叉树(括号表达法)
root(lch,rch)像这样的格式,其中lch,rch本身也是这样的格式(递归定义)。整个过程类似于先序遍历,先打印根节点,再打印左子树,再打印右子树。
注意格式,如果缺少左、右子树用空格代替(如root( ,rch)),如果都没有,则括号也省掉。
//打印表达式二叉树
void PrintTree(int root) { int flag = 0; //标记是否有左右子树
printf("%c", op[root]); if (lch[root] > 0) flag += 1; if (rch[root] > 0) flag += 2; if (flag == 0) return; if (flag == 1) //只有左子树
{ printf("("); PrintTree(lch[root]); printf(", )"); } if (flag == 2) //只有右子树
{ printf("( ,"); PrintTree(rch[root]); printf(")"); } if (flag == 3) //左右子树都有
{ printf("("); PrintTree(lch[root]); printf(","); PrintTree(rch[root]); printf(")"); } }
最一个加一个认真写的 胡乱写的能实现嵌套括号、多位数相加减乘除的完整代码
1 #include<stdio.h>
2 #include<string>
3
4 const int maxn = 1000 + 10; 5 char expr[maxn]; //原表达式
6 int str[maxn]; //转换后的表达式
7 int lch[maxn + 1], rch[maxn + 1],op[maxn + 1]; //每个结点的左右子结点编号和字符
8 bool is_alpha1[maxn], is_alpha2[maxn]; //是否为操作符或括号,前者对s数组,后者对op数组
9 int nc = 0; //结点数
10 int cnt = 0; //转换后字符串的长度 11
12 // 把表达式exp转化成参数形式,并存到str中
13 void analyse(char* expr) 14 { 15 int len = strlen(expr); 16 int i = 0; 17 while(i < len) 18 { 19 if (!isdigit(expr[i])) 20 { 21 str[cnt] = expr[i++]; 22 is_alpha1[cnt++] = true; 23 } 24 else
25 { 26 int tmp = 0; 27 while (isdigit(expr[i])) 28 { 29 tmp = tmp * 10 + expr[i] - '0'; 30 i++; 31 } 32 str[cnt] = tmp; 33 is_alpha1[cnt++] = false; 34 } 35 } 36 } 37
38 //表达式转表达式树
39 int build_tree(int* s, int x, int y) 40 { 41 int i, c1=-1, c2=-1, p=0; 42 int u; 43 if(y-x == 1) //仅一个字符,建立单独结点
44 { 45 u = ++nc; 46 lch[u] = rch[u] = 0; 47 op[u] = s[x]; 48 if (is_alpha1[x]) is_alpha2[u] = true; 49 return u; 50 } 51
52 for (i = x; i < y; i++) //寻找根节点的位置
53 { 54 if(s[i] == '(' && is_alpha1[i]) p++; 55 if(s[i] == ')' && is_alpha1[i]) p--; 56 if((s[i] == '+' || s[i] == '-') && is_alpha1[i]) if (!p) c1 = i; 57 if((s[i] == '*' || s[i] == '/') && is_alpha1[i]) if (!p) c2 = i; 58 } 59 if (c1 < 0) c1 = c2; //找不到括号外的加减号,就用乘除号
60 if(c1 < 0) return build_tree(s, x+1, y-1); //整个表达式被一对括号括起来
61 u = ++nc; 62 lch[u] = build_tree(s, x, c1); 63 rch[u] = build_tree(s, c1+1, y); 64 op[u] = s[c1]; 65 if (is_alpha1[c1]) is_alpha2[u] = true; 66 return u; 67 } 68
69 //先序遍历
70 void PreOrder(int root) 71 { 72 if(!is_alpha2[root]) printf("%d ", op[root]); 73 else printf("%c ", op[root]); 74
75 if (lch[root] > 0) PreOrder(lch[root]); 76 if (rch[root] > 0) PreOrder(rch[root]); 77 } 78
79 //中序遍历
80 void InOrder(int root) 81 { 82 if (lch[root] > 0) InOrder(lch[root]); 83
84 if (!is_alpha2[root]) printf("%d ", op[root]); 85 else printf("%c ", op[root]); 86
87 if (rch[root] > 0) InOrder(rch[root]); 88 } 89
90 //后序遍历
91 void PostOrder(int root) 92 { 93 if (lch[root] > 0) PostOrder(lch[root]); 94 if (rch[root] > 0) PostOrder(rch[root]); 95
96 if (!is_alpha2[root]) printf("%d ", op[root]); 97 else printf("%c ", op[root]); 98 } 99
100 //根据表达式二叉树求值
101 int cal(int root) 102 { 103 int ans = 0; 104 int ch = op[root]; 105 if (!is_alpha2[root]) return ch; 106 switch (ch) 107 { 108 case '+': ans = cal(lch[root]) + cal(rch[root]); break; 109 case '-': ans = cal(lch[root]) - cal(rch[root]); break; 110 case '*': ans = cal(lch[root]) * cal(rch[root]); break; 111 case '/': ans = cal(lch[root]) / cal(rch[root]); break; 112 } 113 return ans; 114 } 115
116 //打印表达式二叉树
117 void PrintTree(int root) 118 { 119 int flag = 0; //标记是否有左右子树
120 if (!is_alpha2[root]) printf("%d", op[root]); 121 else printf("%c", op[root]); 122
123 if (lch[root] > 0) flag += 1; 124 if (rch[root] > 0) flag += 2; 125 if (flag == 0) return; 126 if (flag == 1) //只有左子树
127 { 128 printf("("); 129 PrintTree(lch[root]); 130 printf(", )"); 131 } 132 if (flag == 2) //只有右子树
133 { 134 printf("( ,"); 135 PrintTree(rch[root]); 136 printf(")"); 137 } 138 if (flag == 3) //左右子树都有
139 { 140 printf("("); 141 PrintTree(lch[root]); 142 printf(","); 143 PrintTree(rch[root]); 144 printf(")"); 145 } 146 } 147
148 int main() 149 { 150 printf("表达式:"); 151 scanf("%s", expr); 152
153 analyse(expr); //转化
154
155
156 build_tree(str, 0, cnt); //建树
157
158 int root = 1; 159 printf("先序遍历:"); PreOrder(root); printf("\n"); //三种遍历
160 printf("中序遍历:"); InOrder(root); printf("\n"); 161 printf("后序遍历:"); PostOrder(root); printf("\n"); 162
163 printf("表达式二叉树:"); PrintTree(root); printf("\n"); //打印表达式二叉树
164
165 int ans; 166 ans = cal(root); //表达式求值
167 printf("表达式值:%d\n", ans); 168
169 return 0; 170 }