表达式树处理前中后缀表达式

表达式树

例题:https://www.luogu.com.cn/problem/P1175
在写这道题的时候发现了表达式树这个东西,然后在查找文章的时候貌似没有找到建树的详细内容,于是有了这篇文章,,
前置知识

  • 前缀表达式:将运算符写在两个操作数之前的表达式。和后缀表达式一样,前缀表达式没有括号,运算符没有优先级,能严格按照从右到左的顺序计算。
  • 中缀表达式:将运算符放在两个操作数中间,许多情况下为了确定运算顺序,括号是不可少的。(我们平常写的表达式)
  • 后缀表达式:式中不再引用括号,运算符号放在两个运算对象之后,所有计算按运算符号出现的顺序,严格地由左而右新进行(不用考虑运算符的优先级)。

例子

  • 中缀表达式: ( 4 + 1 ∗ ( 5 − 2 ) ) − 6 / 3 (4+1*(5-2))-6/3 (4+1(52))6/3

  • 前缀表达式:- + 4 * 1 - 5 2 / 6 3

  • 后缀表达式:4 1 5 2 - * + 6 3 / -

算数表达式是分层的递归结构,一个运算符作用于相应的运算对象,其运算对象又可以是任意复杂的表达式。树的递归结构正好用来表示这种表达式。下面只讨论二元表达式。
二元表达式可以很自然的联系到二叉树:以基本运算对象作为叶节点中的数据;以运算符作为非叶节点中的数据,其两棵子树是它的运算对象,子树可以是基本运算对象,也可以是复杂表达式。如图是一个表达式树。

首先将每个运算加上括号,区分优先级,得到 ( 4 + ( 1 ∗ ( 5 − 2 ) ) ) − ( 6 / 3 ) (4+(1*(5-2)))-(6/3) (4+(1(52)))(6/3)
括号外的-优先级最低,作为根节点, ( 4 + ( 1 ∗ ( 5 − 2 ) ) ) (4+(1*(5-2))) (4+(1(52)))作为左子树, ( 6 / 3 ) (6/3) (6/3)作为右子树;
递归的转换 4 + ( 1 ∗ ( 5 − 2 ) ) 4+(1*(5-2)) 4+(1(52)),+最为根节点,4是左子树, ( 1 ∗ ( 5 − 2 ) ) (1*(5-2)) (1(52))是右子树。 ∗ * 是右子树的根节点, 1 1 1是左子树, ( 5 − 2 ) (5-2) (52)是右子树。最后计算 ( 5 − 2 ) (5-2) (52) − - 是根节点,5是左子树,2是右子树。
img
以上参考自https://blog.csdn.net/fireflylane/article/details/83017889

  • 表达式树的先序遍历:前缀表达式
  • 表达式树的中序遍历:中缀表达式
  • 表达式树的后序遍历:后缀表达式

建树:(通过中缀表达式)

  1. 找根:

​ 有加减找最后一个加减,

​ 有乘除找最后一个乘除,

​ 否则找最后一个乘方

  1. 去掉(xxxxx) 型的相匹配的括号

找到根后,直接分开两边继续递归

  • 树的结构
  • 叶子结点的数值num记录
  • 每个节点的符号(叶子结点0~9用空格记录)

代码

建树

int priority(char c) {  // 设置优先级
    if (c == '^')
        return 3;  // 乘方
    if (c == '*' || c == '/')
        return 2;
    if (c == '+' || c == '-')
        return 1;
    if (c == '(' || c == ')')
        return 0;
    return -1;
}
void build_tree(int l, int r) {
    if (l == r) {
        num[++cnt] = s[l] - '0';
        ch[cnt] = ' ';  // 标志叶子节点
        return;
    }
    if (s[l] == '(') {  // 判断是否为(xxxxxxx)型
        int k = 1;
        for (int i = l + 1; i <= r; i++) {
            if (s[i] == '(')
                k++;
            else if (s[i] == ')')
                k--;
            if (k == 0) {
                if (i == r)
                    l++, r--;  // 是(xxxx)型,将两边括号去掉
                break;
            }
        }
    }
    int p, nowp = 5;  // p:找到的根, powp:当前的优先级
    for (int i = l; i <= r; i++) {
        if (s[i] == '(') {  // 跳过括号
            int k = 1, j;
            for (j = i + 1; j <= r; j++) {
                if (s[j] == '(')
                    k++;
                else if (s[j] == ')')
                    k--;
                if (k == 0)
                    break;
            }
            i = j;
            continue;
        }
        if (s[i] >= '0' && s[i] <= '9')  // 跳过数字
            continue;
        if (priority(s[i]) <= nowp)  // 找到优先级最低的最右的点的下标
            p = i, nowp = priority(s[i]);
    }
    int x = ++cnt;
    ch[x] = s[p];
    ls[x] = cnt + 1;
    build_tree(l, p - 1);
    rs[x] = cnt + 1;
    build_tree(p + 1, r);
}

得到后缀表达式:

void PostOrderPrint(int u) {  // 后序遍历输出后缀表达式
    if (ch[u] == ' ') {       // 数字一定为叶子节点,叶子节点一定为数字
        cout << num[u] << " ";
        return;
    }
    PostOrderPrint(ls[u]);
    PostOrderPrint(rs[u]);
    cout << ch[u] << " ";
}

得到前缀表达式:

void PreOrderPrint(int u) {  // 先序遍历输出前缀表达式
    if (ch[u] == ' ') {      // 数字一定为叶子节点,叶子节点一定为数字
        cout << num[u] << " ";
        return;
    }
    cout << ch[u] << " ";
    PreOrderPrint(ls[u]);
    PreOrderPrint(rs[u]);
}

按后缀表达式计算:

int calc(int a, int b, char gk) { // 一次计算,计算a gk(+-*/^) b
    switch (gk) {
        case '+':
            return a + b;
        case '-':
            return a - b;
        case '*':
            return a * b;
        case '/':
            return a / b;
        case '^':
            return pow(a, b);
        default:
            return -1;
    }
}
void Post_calc(int x) {  // 后缀表达式计算
    if (ch[x] == ' ') { // 根节点
        return;
    }
    Post_calc(ls[x]); // 先计算左子树
    Post_calc(rs[x]); // 再计算右子树
    num[x] = calc(num[ls[x]], num[rs[x]], ch[x]); // 更新当前节点
    ch[x] = ' '; // 标志该节点为数字(当前的叶子节点)
    PostOrderPrint(1), cout << "\n";  // 输出计算过程
}

按前缀表达式计算:

void Pre_calc(int x) { // 前缀表达式计算
    if (ch[x] == ' ') {
        return;
    }
    Pre_calc(rs[x]); // 前缀表达式先计算右子树
    Pre_calc(ls[x]); // 再计算左子树
    num[x] = calc(num[ls[x]], num[rs[x]], ch[x]); // 更新当前节点
    ch[x] = ' '; // 标志该节点为数字(当前的叶子节点)
    PreOrderPrint(1), cout << "\n";  // 输出计算过程
}

完整代码

https://www.luogu.com.cn/paste/io4drydz

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

self_disc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值