好的 👍 我来总结一下 生成表达式树 的常见方法(只讲思路,不上代码):
1. 基于后缀表达式(逆波兰式)
这是最常见也最简单的方法。
-
步骤:
-
先把中缀表达式(如
3 + 5 * 2)转为后缀表达式(3 5 2 * +),通常用 栈 实现。 -
遍历后缀表达式:
- 遇到操作数(数字/变量),创建一个叶子节点,压入栈。
- 遇到运算符,弹出两个节点(右操作数在栈顶),生成一个新运算符节点,把两个节点作为它的左右子树,再把这个新节点压入栈。
-
最后栈中唯一的节点就是整棵表达式树的根。
-
-
优点:逻辑清晰,栈结构天然适合。
-
应用:编译器、计算器。
2. 基于递归下降 / 文法解析
-
将表达式看作一棵语法树,利用递归来解析。
-
例子:
- 一个表达式
E可以分为:E = T (+/- T)* - 一个项
T可以分为:T = F (*// F)* - 一个因子
F可以是:数字 / 变量 / 括号里的子表达式
- 一个表达式
-
递归解析时,每一层返回一个子树节点,最后拼成完整的树。
-
优点:结构清晰,和正规语法(BNF)一一对应。
-
应用:编译器前端、解释器。
3. 运算符优先级栈法(Shunting-yard)
-
由 Dijkstra 提出的著名算法,本质是把中缀转后缀(再转树)。
-
步骤:
-
准备两个栈:运算符栈、操作数栈。
-
依次读入符号:
- 数字 → 压入操作数栈。
- 运算符 → 根据优先级处理,出栈已有运算符并生成树节点。
- 括号 → 特殊处理。
-
最终把所有运算符出栈,并拼接成树。
-
-
优点:不必先生成后缀表达式,直接就能建树。
-
应用:表达式求值、解释器。
4. 自顶向下(分治法)
-
如果你能快速找到当前表达式中 优先级最低的主运算符(比如在
(3+5)*2中的*),就可以:- 把它作为根节点。
- 左右部分递归生成子树。
-
例子:
- 表达式
3 + 5 * 2 - 4 - 最外层的最低优先级是
+和-,最右的-成为根 - 左边
3+5*2递归成一棵子树,右边4变叶子
- 表达式
-
优点:直观,完全递归化。
-
缺点:实现时需要小心括号和优先级。
5. 抽象语法树 (AST) 生成
- 在编译原理里,语法分析器(Parser)生成的就是 AST,本质上就是表达式树。
- 一般通过 词法分析 → 语法分析,然后根据语法规则建树。
- 特点:相比一般表达式树,AST 会忽略括号、某些中间节点,只保留运算逻辑。
📌 总结:
- 如果是实际写程序 → 推荐 方法 1(后缀表达式 + 栈) 或 方法 3(Shunting-yard)。
- 如果是研究语法 / 编译原理 → 用 方法 2(递归下降) 或 方法 5(AST 生成)。
- 如果只是想理解表达式结构 → 方法 4(分治法) 最直观。
要不要我帮你画几个 同一个表达式在不同方法下生成的树的对比图?