当前位置: 首页 > news >正文

递归下降算法

递归下降算法长用于数据表达式匹配。之前只听说和使用过递归算法,递归算法就是在函数内部调用函数本身。

在实际工作中有下边这样的需求,想不到用什么算法来实现,故将需求输入给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;
}

http://www.xdnf.cn/news/279469.html

相关文章:

  • 结构型模式:外观模式
  • Python 数据智能实战 (12):效果评估 - 超越传统指标
  • 平台介绍-开放API接口-IO说明
  • 阿里云服务器全栈技术指导手册(2025版)
  • 基于 PyQt 的YOLO目标检测可视化界面+ nuitka 打包
  • Spring AI 实战:第六章、Spring AI源码浅析之一山可容二虎
  • 实验四 增强型可靠文件传输系统
  • 电容电阻作用
  • PostgreSQL 表的年龄(age)详解
  • 从 Java 开发到 AI 工程师:全面学习指南
  • C++多继承陷阱全解:虚析构函数与虚表布局的工程实践
  • 方案精读:业财融合转型路径和华为实践【附全文阅读】
  • 质检报告警示:亚马逊等平台3成节能插座不达标
  • [特殊字符]Spring Boot 后台使用 EasyExcel 实现数据报表导出(含模板、样式、美化)
  • 【iOS】 方法交换
  • Linux文件权限管理:chmod修改权限 与 chown修改所有者
  • Android第三次面试总结之网络篇补充
  • 力扣-链表-2 两数相加
  • 情绪ABC——AI与思维模型【93】
  • # 基于SIFT的图像相似性检测与拼接:Python实现与解析
  • 精品,CentOS7.9 Yum安装Nginx,并配置JSON日志格式
  • Matlab/Simulink - BLDC直流无刷电机仿真基础教程(七) - 波形解析专题P2
  • Java 中使用 Callable 创建线程的方法
  • FastApi快速实践
  • React class 的组件库与函数组件适配集成
  • C++函数总结
  • 【Java学习笔记】方法重载
  • 以太坊智能合约开发框架:Hardhat v2 核心功能从入门到基础教程
  • 深入浅出数据库管理系统
  • 工程师 - 汽车分类