递归下降算法
递归下降算法长用于数据表达式匹配。之前只听说和使用过递归算法,递归算法就是在函数内部调用函数本身。
在实际工作中有下边这样的需求,想不到用什么算法来实现,故将需求输入给deepseek,deepseek给出的答案中就用到了递归下降算法:
在软件开发中有这样一个需求,开发语言是c++,用户可以通过配置指定任务依赖的事件,比如配置e1|e2|e3的意思是,e1、e2、e3 3个事件,只要有一个到来,那么条件就是满足的,任务可以执行;再比如,用户配置e1&e2&e3,表示,只有e1、e2、e3 3个事件都道来,任务才可以执行;在比如,用户还可以配置(2*e1 | e2) & e3,这样复杂的逻辑,表示e3是必要的,另外e1到来两次或者e2到来一次也是必要的。事件的个数不仅仅是3个,还可以更多,逻辑关系包含|表示或,&表示与,*表示时间到来的次数,逻辑关系可以任意组合。对于这样的需求,代码应该怎么实现。
递归算法:
函数内部调用函数本身,即函数a中调用函数a。
递归下降算法:
函数a调用函数b,函数b调用函数c,...函数n调用函数n+1,函数n+1调用函数a。
代码记录如下,其中类Parser中使用递归下降算法。
#include <memory>
#include <string>
#include <map>
#include <vector>
#include <cctype>
#include <stdexcept>
#include <iostream>// ========================== 抽象语法树(AST)节点定义 ==========================
// 所有节点的基类,定义统一的评估接口
class Node {
public:virtual ~Node() = default;// 评估方法:根据当前事件状态判断条件是否满足virtual bool evaluate(const std::map<std::string, int>& events) const = 0;
};// 事件节点:表示需要特定次数的事件
class EventNode : public Node {std::string name; // 事件名称(如"e1")int required; // 需要达到的次数
public:EventNode(std::string n, int r) : name(std::move(n)), required(r) {}bool evaluate(const std::map<std::string, int>& events) const override {auto it = events.find(name);// 检查事件是否已到达且次数满足要求return it != events.end() && it->second >= required;}
};// 逻辑与节点:左右子节点都需要满足
class AndNode : public Node {std::unique_ptr<Node> left; // 左子树std::unique_ptr<Node> right; // 右子树
public:AndNode(std::unique_ptr<Node> l, std::unique_ptr<Node> r): left(std::move(l)), right(std::move(r)) {}bool evaluate(const std::map<std::string, int>& events) const override {// 递归评估左右子树是否都满足return left->evaluate(events) && right->evaluate(events);}
};// 逻辑或节点:左右子节点任一满足
class OrNode : public Node {std::unique_ptr<Node> left; // 左子树std::unique_ptr<Node> right; // 右子树
public:OrNode(std::unique_ptr<Node> l, std::unique_ptr<Node> r): left(std::move(l)), right(std::move(r)) {}bool evaluate(const std::map<std::string, int>& events) const override {// 递归评估左右子树是否至少有一个满足return left->evaluate(events) || right->evaluate(events);}
};// ========================== 词法分析器(Tokenizer) ==========================
// Token类型枚举
enum TokenType {NUMBER, // 数字(用于次数)EVENT, // 事件名称(如e1)AND, // 与操作符 &OR, // 或操作符 |MULTIPLY, // 乘号 *(用于次数)LPAREN, // 左括号 (RPAREN, // 右括号 )END, // 输入结束ERROR // 错误token
};// Token结构体:包含类型、原始值和数字值(如果是数字)
struct Token {TokenType type;std::string value; // 原始字符串值int num = 0; // 如果是数字token,存储数值
};// 词法分析器:将输入字符串转换为token序列
class Tokenizer {std::string input; // 原始输入字符串size_t pos = 0; // 当前解析位置public:Tokenizer(std::string str) : input(std::move(str)) {}// 获取下一个tokenToken next() {// 跳过空白字符while (pos < input.size() && isspace(input[pos])) ++pos;if (pos >= input.size()) return {END, ""};char c = input[pos++];// 处理单字符tokenswitch(c) {case '(': return {LPAREN, "("};case ')': return {RPAREN, ")"};case '&': return {AND, "&"};case '|': return {OR, "|"};case '*': return {MULTIPLY, "*"};}// 处理数字(次数)if (isdigit(c)) {size_t start = pos-1;while (pos < input.size() && isdigit(input[pos])) ++pos;int num = std::stoi(input.substr(start, pos-start));return {NUMBER, "", num};}// 处理事件名称(字母开头,包含字母数字和下划线)if (isalpha(c)) {size_t start = pos-1;while (pos < input.size() && (isalnum(input[pos]) || input[pos] == '_')) ++pos;return {EVENT, input.substr(start, pos-start)};}// 未知字符return {ERROR, ""};}
};// ========================== 语法分析器(Parser) ==========================
// 递归下降解析器:将token流转换为AST
class Parser {Tokenizer& tokenizer; // 词法分析器引用Token current; // 当前处理的token// 前进到下一个tokenvoid advance() { current = tokenizer.next(); }// 表达式解析(处理OR操作)std::unique_ptr<Node> parseExpr() {auto left = parseAnd();// 循环处理连续的OR操作(左结合)while (current.type == OR) {advance(); // 跳过ORauto right = parseAnd();// 构建OR节点,左子树为之前的表达式,右子树为新的AND表达式left = std::make_unique<OrNode>(std::move(left), std::move(right));}return left;}// AND表达式解析(处理AND操作)std::unique_ptr<Node> parseAnd() {auto left = parseTerm();// 循环处理连续的AND操作(左结合)while (current.type == AND) {advance(); // 跳过ANDauto right = parseTerm();// 构建AND节点left = std::make_unique<AndNode>(std::move(left), std::move(right));}return left;}// 项解析(处理次数*事件格式)std::unique_ptr<Node> parseTerm() {// 检查是否是数字开头(次数定义)if (current.type == NUMBER) {int count = current.num;advance(); // 跳过数字if (current.type != MULTIPLY) throw std::runtime_error("Expected * after number");advance(); // 跳过*if (current.type != EVENT) throw std::runtime_error("Expected event after *");// 创建带次数的事件节点auto node = std::make_unique<EventNode>(current.value, count);advance(); // 跳过事件名return node;}return parseFactor();}// 因子解析(处理括号和基本事件)std::unique_ptr<Node> parseFactor() {if (current.type == LPAREN) {advance(); // 跳过(auto node = parseExpr(); // 递归解析括号内表达式if (current.type != RPAREN)throw std::runtime_error("Mismatched parentheses");advance(); // 跳过)return node;}if (current.type == EVENT) {// 创建默认次数为1的事件节点auto node = std::make_unique<EventNode>(current.value, 1);advance(); // 跳过事件名return node;}throw std::runtime_error("Unexpected token");}public:Parser(Tokenizer& t) : tokenizer(t) { advance(); } // 初始化时获取第一个tokenstd::unique_ptr<Node> parse() {return parseExpr();}
};// ========================== 使用示例 ==========================
int main() {// 示例1: (2*e1 | e2) & e3{std::cout << "===== 测试用例1 =====" << std::endl;std::string config = "(2*e1 | e2) & e3";Tokenizer tokenizer(config);Parser parser(tokenizer);auto ast = parser.parse();// 测试用例1:e1触发2次,e3触发1次 -> 应满足std::map<std::string, int> case1 = {{"e1", 2}, {"e3", 1}}; std::cout << "Case1: " << ast->evaluate(case1) << " (预期: true)\n";// 测试用例2:e2触发1次,e3触发1次 -> 应满足std::map<std::string, int> case2 = {{"e2", 1}, {"e3", 1}}; std::cout << "Case2: " << ast->evaluate(case2) << " (预期: true)\n";// 测试用例3:e1触发1次,e3触发1次 -> 不满足std::map<std::string, int> case3 = {{"e1", 1}, {"e3", 1}}; std::cout << "Case3: " << ast->evaluate(case3) << " (预期: false)\n";}// 示例2: a | b&(c | 3*d){std::cout << "\n===== 测试用例2 =====" << std::endl;std::string config = "a | b&(c | 3*d)";Tokenizer tokenizer(config);Parser parser(tokenizer);auto ast = parser.parse();// 测试用例1:a触发1次 -> 应满足std::map<std::string, int> case1 = {{"a", 1}}; std::cout << "Case1: " << ast->evaluate(case1) << " (预期: true)\n";// 测试用例2:b触发1次,d触发3次 -> 应满足std::map<std::string, int> case2 = {{"b",1}, {"d",3}}; std::cout << "Case2: " << ast->evaluate(case2) << " (预期: true)\n";// 测试用例3:b触发1次,c触发1次 -> 应满足std::map<std::string, int> case3 = {{"b",1}, {"c",1}}; std::cout << "Case3: " << ast->evaluate(case3) << " (预期: true)\n";// 测试用例4:b触发1次,d触发2次 -> 不满足std::map<std::string, int> case4 = {{"b",1}, {"d",2}}; std::cout << "Case4: " << ast->evaluate(case4) << " (预期: false)\n";}return 0;
}