表达式树

二叉树是表达式处理的常用工具。例如,表达式

  1. a+b*(c-d)-e/f

image.png
其中,每个非叶子结点表示一个运算符,左子树是第一个运算数对应的表达式,而右子树则是第二个运算数对应的表达式。如何给一个表达式简历表达式树呢?方法有很多,这里只介绍一种:找到“最后计算”的运算符(它是整颗表达式树的根),然后递归处理。

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N = 1010;
  4. int lch[N], rch[N], idx, root;
  5. char op[N], s[N];
  6. int build_tree(char *s, int x, int y){
  7. int c1 = -1, c2 = -1, p = 0;
  8. int u;
  9. if (y - x == 1){ // 只有一个字符了
  10. u = ++idx;
  11. lch[u] = rch[u] = -1;
  12. op[u] = s[x];
  13. return u;
  14. }
  15. for (int i = x; i < y; i++){
  16. if (s[i] == '(') p++;
  17. if (s[i] == ')') p--;
  18. if (s[i] == '+' || s[i] == '-')
  19. if (!p) c1 = i;
  20. if (s[i] == '*' || s[i] == '/')
  21. if (!p) c2 = i;
  22. }
  23. // 找不到括号外的加减,就用乘除
  24. if (c1 < 0) c1 = c2;
  25. // 整个表达式被一对括号括起来,往里缩一位
  26. if (c1 < 0) return build_tree(s, x + 1, y - 1);
  27. u = ++idx;
  28. lch[u] = build_tree(s, x, c1);
  29. rch[u] = build_tree(s, c1 + 1, y);
  30. op[u] = s[c1];
  31. return u;
  32. }
  33. void in_order(int u){
  34. if (lch[u] == -1 && rch[u] == -1){
  35. cout << op[u];
  36. return ;
  37. }
  38. in_order(lch[u]);
  39. cout << op[u];
  40. in_order(rch[u]);
  41. }
  42. void pre_order(int u){
  43. if (lch[u] == -1 && rch[u] == -1){
  44. cout << op[u];
  45. return ;
  46. }
  47. cout << op[u];
  48. pre_order(lch[u]);
  49. pre_order(rch[u]);
  50. }
  51. int main(){
  52. scanf("%s", s);
  53. root = build_tree(s, 0, strlen(s));
  54. in_order(root);
  55. puts("");
  56. pre_order(root);
  57. return 0;
  58. }
  59. /*
  60. a+b*(c-d)-e/f
  61. */

上述代码是如何寻找“最后一个运算符”的呢?代码用了一个变量p,只有当p=0时才考虑这个运算符。为什么呢?因为括号里的运算符一定不是最后计算的,应当忽略。例如,(a+b)*c中虽然有一个加号,但却是在括号里的,实际上比它优先级高的乘号才是最后计算的。由于加减和乘除号都是左结合的,最后一个运算符才是最后计算的,所以用两个变量c1和c2分别记录“最右”出现的加减号和乘除号。
再接下来的代码就不难理解了:如果括号外有加减号,它们肯定最后计算;但如果没有加减号,就需要考虑乘除号(if(c1 < 0) c1 = c2);如果全都没有,说明整个表达式外面被一对括号括起来,把它去掉后递归调用。这样,就找到了最后计算的运算符s[c1],它的左子树是区间[x, c1],右子树是区间[c1+1, y]。