P8815 [CSP-J 2022] 逻辑表达式 题解

· · 题解

Tips:本人 AC 这道题目的代码属于图灵完备的编译器。

题目回顾

“逻辑表达式”题目要求我们计算一个给定的逻辑表达式的值,并统计计算过程中出现的“短路”次数。逻辑表达式由 01& (与)、| (或) 和括号组成。需要注意的是,& 的优先级高于 |,同级运算从左到右,括号优先。

这道题看起来好难啊。

那我们写一个编译器吧。

短路是指在 a & b 中,若 a0 则整个表达式值为 0,不再计算 b;以及在 a | b 中,若 a1 则整个表达式值为 1,不再计算 b

解题思路

这道题的核心在于如何正确地解析表达式,并按照规定的运算顺序进行计算。直接模拟计算过程较为繁琐,容易出错。而使用 AST 可以将表达式的结构清晰地表示出来,方便我们进行计算和统计。

什么是抽象语法树 (AST)

抽象语法树 (Abstract Syntax Tree, AST) 是源代码语法结构的一种树状表示形式。它以树状的形式表现编程语言的语法结构,每个节点都代表源代码中的一个语法结构。在逻辑表达式中,我们可以用 AST 来表示运算的优先级和嵌套关系。

例如,表达式 0&(1|0)|(1|1|1&0) 的 AST 可以大致表示为:

      |
     / \
    &   |
   / \ / \
  0   | 1   |
     / \   / \
    1   0 1   &
           / \
          1   0

代码解析

我们将分解代码的各个部分,并解释其功能以及与 AST 和编译原理概念的联系。

1. 头文件和全局变量

#include <iostream>
#include <string>
#include <stack>
#include <vector>

using namespace std;

// 定义全局变量统计短路次数
int and_short_circuit = 0;
int or_short_circuit = 0;

2. AST 节点定义

// 定义 AST 节点类型
enum class NodeType {
    VALUE,
    AND,
    OR
};

// 定义 AST 节点结构
struct Node {
    NodeType type;
    int value; // 仅当 type 为 VALUE 时有效
    Node* left;
    Node* right;

    Node(NodeType t, int v = 0, Node* l = nullptr, Node* r = nullptr) : type(t), value(v), left(l), right(r) {}
};

3. 中缀表达式转后缀表达式 (infixToPostfix)

vector<char> infixToPostfix(const string& infix) {
    // ... 代码 ...
}

这一部分实现了经典的调度场算法,将中缀表达式转换为后缀表达式。后缀表达式也称为逆波兰表达式,它将操作符放在操作数之后,更便于计算机处理。

4. 构建 AST (buildAST)

Node* buildAST(const vector<char>& postfix) {
    // ... 代码 ...
}

此函数根据后缀表达式构建 AST。

5. 计算 AST 的值并统计短路 (evaluateAST)

int evaluateAST(Node* node, bool is_short_circuit_parent = false) {
    // ... 代码 ...
}

此函数递归地计算 AST 的值,并统计短路次数。

6. 释放 AST (deleteAST)

void deleteAST(Node* node) {
    // ... 代码 ...
}

此函数递归地释放 AST 占用的内存,防止内存泄漏。

7. 主函数 (main)

int main() {
    string infix;
    cin >> infix;

    vector<char> postfix = infixToPostfix(infix);
    Node* root = buildAST(postfix);

    int result = evaluateAST(root);

    cout << result << endl;
    cout << and_short_circuit << " " << or_short_circuit << endl;

    deleteAST(root);

    return 0;
}

主函数负责读取输入,调用各个函数完成表达式的转换、AST 的构建、计算和结果输出,以及最后的 AST 释放。

评测记录:https://www.luogu.com.cn/record/197925479