表达式树
例题:https://www.luogu.com.cn/problem/P1175
在写这道题的时候发现了表达式树这个东西,然后在查找文章的时候貌似没有找到建树的详细内容,于是有了这篇文章,,
前置知识:
- 前缀表达式:将运算符写在两个操作数之前的表达式。和后缀表达式一样,前缀表达式没有括号,运算符没有优先级,能严格按照从右到左的顺序计算。
- 中缀表达式:将运算符放在两个操作数中间,许多情况下为了确定运算顺序,括号是不可少的。(我们平常写的表达式)
- 后缀表达式:式中不再引用括号,运算符号放在两个运算对象之后,所有计算按运算符号出现的顺序,严格地由左而右新进行(不用考虑运算符的优先级)。
例子
-
中缀表达式: ( 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 5 2 - * + 6 3 / -
算数表达式是分层的递归结构,一个运算符作用于相应的运算对象,其运算对象又可以是任意复杂的表达式。树的递归结构正好用来表示这种表达式。下面只讨论二元表达式。
二元表达式可以很自然的联系到二叉树:以基本运算对象作为叶节点中的数据;以运算符作为非叶节点中的数据,其两棵子树是它的运算对象,子树可以是基本运算对象,也可以是复杂表达式。如图是一个表达式树。
首先将每个运算加上括号,区分优先级,得到
(
4
+
(
1
∗
(
5
−
2
)
)
)
−
(
6
/
3
)
(4+(1*(5-2)))-(6/3)
(4+(1∗(5−2)))−(6/3)
括号外的-优先级最低,作为根节点,
(
4
+
(
1
∗
(
5
−
2
)
)
)
(4+(1*(5-2)))
(4+(1∗(5−2)))作为左子树,
(
6
/
3
)
(6/3)
(6/3)作为右子树;
递归的转换
4
+
(
1
∗
(
5
−
2
)
)
4+(1*(5-2))
4+(1∗(5−2)),+最为根节点,4是左子树,
(
1
∗
(
5
−
2
)
)
(1*(5-2))
(1∗(5−2))是右子树。
∗
*
∗是右子树的根节点,
1
1
1是左子树,
(
5
−
2
)
(5-2)
(5−2)是右子树。最后计算
(
5
−
2
)
(5-2)
(5−2),
−
-
−是根节点,5是左子树,2是右子树。
以上参考自https://blog.csdn.net/fireflylane/article/details/83017889
- 表达式树的先序遍历:前缀表达式
- 表达式树的中序遍历:中缀表达式
- 表达式树的后序遍历:后缀表达式
建树:(通过中缀表达式)
- 找根:
有加减找最后一个加减,
有乘除找最后一个乘除,
否则找最后一个乘方
- 去掉
(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"; // 输出计算过程
}