首页/新闻资讯/正文详情

VC++实现正则表达式到NFA转换:Thompson构造法工程实践

发布时间:2026/9/27 21:37:29 来源:云帆数科 栏目:资讯中心
VC++实现正则表达式到NFA转换:Thompson构造法工程实践
1. 项目概述从正则表达式到有穷自动机的工程实践在文本处理、编译器前端设计乃至安全扫描等领域正则表达式Regular Expression, Regex都是我们手中一把锋利的瑞士军刀。但你是否想过当你写下a(b|c)*d这样一段模式时计算机底层是如何一步步理解并执行匹配的这背后正是“有穷自动机”Finite Automaton在默默工作。今天我们不谈枯燥的理论而是聚焦于一个具体的工程实现如何在经典的 VC 开发环境中亲手搭建一座从正则表达式字符串描述到有穷自动机内存数据结构的桥梁。这个项目的核心价值在于“解析”与“实现”。它不仅仅是理解“正则引擎是如何工作的”这一理论问题更是解决“如果我需要为一个特定场景定制匹配规则甚至优化匹配性能我该如何从最底层构建自己的工具”这一实践问题。无论是希望深入理解编译原理的同学还是需要在C项目中嵌入高性能、可定制的文本匹配模块的开发者这个实例都能提供一条清晰的路径。我们将使用 VC 作为实现平台一方面是因为其成熟的 IDE 和调试环境对复杂数据结构的构建非常友好另一方面在 Windows 平台进行原生开发时这套技术栈具有直接的实用价值。2. 核心原理与设计思路拆解2.1 为什么需要有穷自动机正则表达式对人类来说是直观的文本模式但对计算机而言它需要被转化为一系列确定的状态和转移条件才能执行。有穷自动机就是这样一个计算模型。它就像一个迷宫里面有若干个房间状态房间之间有门状态转移门上贴着通行条件输入字符。我们从入口初态开始根据输入的字符串一个字符一个字符地选择门走如果能走到指定的出口终态就说明字符串匹配成功。有穷自动机主要分为两类不确定的有穷自动机NFA和确定的有穷自动机DFA。NFA 允许从一个状态在接收同一输入字符时转移到多个可能的下一个状态不确定性甚至可以不消耗输入字符就发生转移ε-转移。DFA 则要求每个状态对每个可能的输入字符有且只有一条转移路径。NFA 更贴近正则表达式的直观结构易于从正则表达式构造而 DFA 的运行效率更高因为每一步都没有选择困难症。因此一个完整的正则引擎通常的构造路径是正则表达式 → NFA → DFA → 最小化 DFA → 最终匹配程序。我们的实例将重点实现前两步即正则表达式到 NFA 的构造。2.2 我们的技术选型VC 与自建数据结构选择 VC这里主要指使用 Visual Studio 进行 C 开发而非其他高级语言或现成库如std::regex来实现有几层考量学习深度使用 C 手动管理内存和构建图结构NFA/DFA本质是图能让你透彻理解自动机中状态、转移边等核心概念的内存表示和生命周期这是使用高级封装库无法获得的体验。控制力与性能自己实现的引擎你可以精确控制匹配算法如回溯或状态机驱动、内存布局针对特定模式进行优化这在追求极致性能的场景如网络入侵检测、实时日志分析中是关键。可移植性与依赖最终生成的是一套纯 C 代码不依赖特定的运行时库除了 C 标准库可以轻松集成到各种项目中。在数据结构设计上核心是状态State和转移边Edge。一个简单的设计如下State 类包含一个唯一ID一个标记是否为接受状态终态的布尔量以及一个转移边的列表。Edge 类包含一个指向目标状态的指针以及一个转移条件。这个条件可以是一个具体的字符Char一个字符范围CharRange或者是特殊的 ε代表空转移。我们将采用“Thompson 构造法”作为从正则表达式到 NFA 的算法。它的核心思想是递归将复杂的正则表达式视为由基本单元单字符、连接、选择、闭包通过运算符组合而成并为每个基本单元构造一个小的 NFA 片段然后按照运算符的语义将这些小片段像搭积木一样组合成最终的 NFA。3. 核心数据结构与算法实现3.1 定义自动机核心类我们首先在头文件中定义核心的数据结构。为了清晰和管理方便我们将所有内容放在一个命名空间内比如RegexToFA。// RegexToFA.h #pragma once #include memory #include vector #include set #include variant namespace RegexToFA { // 转移条件类型单字符、字符范围、空转移(epsilon) using CharType char; // 简单起见用 char struct CharRange { CharType start; CharType end; }; struct Epsilon {}; using TransitionCondition std::variantCharType, CharRange, Epsilon; // 前向声明 class State; // 转移边 struct Edge { std::shared_ptrState target; // 使用智能指针管理状态生命周期 TransitionCondition condition; }; // 状态 class State { public: int id; bool isAccepting; std::vectorEdge outgoingEdges; State(int stateId, bool accepting false) : id(stateId), isAccepting(accepting) {} void addEdge(std::shared_ptrState target, TransitionCondition cond) { outgoingEdges.push_back({std::move(target), cond}); } }; // 有穷自动机这里特指NFA class FiniteAutomaton { public: std::shared_ptrState startState; std::shared_ptrState acceptState; // 对于Thompson构造法我们通常维护一个唯一的接受状态 FiniteAutomaton(std::shared_ptrState start, std::shared_ptrState accept) : startState(std::move(start)), acceptState(std::move(accept)) {} }; } // namespace RegexToFA这里有几个关键点使用了std::variant来优雅地表示多种转移条件避免了复杂的类继承体系。使用std::shared_ptrState来管理状态对象的所有权。因为一个状态可能被多个转移边指向如图中的汇合点共享所有权是合适的选择。FiniteAutomaton类目前只保存了开始状态和接受状态的引用实际上整个 NFA 是通过状态之间的指针链接起来的图。3.2 实现 Thompson 构造法Thompson 构造法为四种基本操作提供构造规则基本字符c构造两个状态通过一条标有字符 ‘c’ 的边连接。连接AB将自动机 A 的接受状态和自动机 B 的开始状态通过 ε 边连接。选择A|B创建新的开始和接受状态从新开始状态用 ε 边连接到 A 和 B 的开始状态从 A 和 B 的接受状态用 ε 边连接到新接受状态。闭包A*创建新的开始和接受状态。添加四条 ε 边新开始→A开始 A接受→新接受 新开始→新接受零次匹配 A接受→A开始循环。我们在RegexToFA.cpp中实现一个ThompsonConstructor类。// RegexToFA.cpp #include RegexToFA.h #include stack #include cassert namespace RegexToFA { class ThompsonConstructor { int nextStateId 0; public: // 辅助函数生成新状态 std::shared_ptrState createState(bool accepting false) { return std::make_sharedState(nextStateId, accepting); } // 1. 构造基本字符 FiniteAutomaton constructFromChar(CharType c) { auto start createState(); auto accept createState(true); start-addEdge(accept, c); return {start, accept}; } // 2. 构造连接 A · B FiniteAutomaton constructConcatenation(FiniteAutomaton A, FiniteAutomaton B) { // 将A的接受状态与B的开始状态用ε边连接并取消A接受状态的接受属性 A.acceptState-isAccepting false; A.acceptState-addEdge(B.startState, Epsilon{}); // 新的接受状态是B的接受状态 return {A.startState, B.acceptState}; } // 3. 构造选择 A | B FiniteAutomaton constructAlternation(FiniteAutomaton A, FiniteAutomaton B) { auto newStart createState(); auto newAccept createState(true); // 原接受状态不再是接受状态 A.acceptState-isAccepting false; B.acceptState-isAccepting false; // 添加四条ε边 newStart-addEdge(A.startState, Epsilon{}); newStart-addEdge(B.startState, Epsilon{}); A.acceptState-addEdge(newAccept, Epsilon{}); B.acceptState-addEdge(newAccept, Epsilon{}); return {newStart, newAccept}; } // 4. 构造闭包 A* FiniteAutomaton constructClosure(FiniteAutomaton A) { auto newStart createState(); auto newAccept createState(true); A.acceptState-isAccepting false; // 添加四条ε边 newStart-addEdge(A.startState, Epsilon{}); // 进入A newStart-addEdge(newAccept, Epsilon{}); // 跳过A零次 A.acceptState-addEdge(newAccept, Epsilon{}); // A结束 A.acceptState-addEdge(A.startState, Epsilon{}); // A循环 return {newStart, newAccept}; } }; } // namespace RegexToFA注意这里的实现为了清晰直接修改了输入参数FiniteAutomaton对象内部状态的属性如isAccepting。在实际更完整的实现中你可能需要更谨慎地处理状态复用和拷贝问题或者采用函数式风格返回全新的自动机。这里我们假设构造过程是顺序的且每个基本单元都是新创建的。3.3 正则表达式语法解析与驱动有了基础构造块我们需要一个解析器Parser来读取正则表达式字符串如a(b|c)*d并根据运算符优先级闭包 连接 选择将其转化为一系列构造操作。这通常涉及将中缀表达式转换为后缀表达式逆波兰表示法或者使用递归下降解析。这里我们实现一个简化的、支持括号、|选择、*闭包和隐式连接并置的递归下降解析器作为驱动。我们增加一个constructFromRegex的入口函数。// 在 RegexToFA.h 中增加声明 FiniteAutomaton constructFromRegex(const std::string regexStr); // 在 RegexToFA.cpp 中实现 namespace { // 辅助解析类封装 ThompsonConstructor 和字符串迭代 class RegexParser { ThompsonConstructor constructor; std::string::const_iterator pos; std::string::const_iterator end; // 解析原子项字符、括号表达式 FiniteAutomaton parseAtom() { if (pos end) { throw std::runtime_error(Unexpected end of regex); } char c *pos; if (c () { pos; // 跳过 ( auto autoM parseAlternation(); // 解析括号内的表达式最高级是选择 if (pos end || *pos ! )) { throw std::runtime_error(Missing )); } pos; // 跳过 ) return autoM; } else if (c \\) { // 简单转义只处理下一个字符 pos; if (pos end) throw std::runtime_error(Escape at end of string); c *pos; return constructor.constructFromChar(c); } else if (c | || c ) || c *) { // 这些字符在原子项中不合法但闭包处理会在上层处理 throw std::runtime_error(std::string(Unexpected character: ) c); } else { // 普通字符 pos; return constructor.constructFromChar(c); } } // 解析闭包原子项后面可能跟着 * FiniteAutomaton parseClosure() { auto autoM parseAtom(); while (pos ! end *pos *) { pos; // 跳过 * autoM constructor.constructClosure(std::move(autoM)); } return autoM; } // 解析连接由一系列闭包项连接而成 FiniteAutomaton parseConcatenation() { // 至少解析一个项 auto autoM parseClosure(); // 继续解析后续的项直到遇到选择符或结尾 while (pos ! end *pos ! | *pos ! )) { auto nextAutoM parseClosure(); autoM constructor.constructConcatenation(std::move(autoM), std::move(nextAutoM)); } return autoM; } // 解析选择最高优先级由一系列连接项用 | 连接 FiniteAutomaton parseAlternation() { auto autoM parseConcatenation(); while (pos ! end *pos |) { pos; // 跳过 | auto rightAutoM parseConcatenation(); autoM constructor.constructAlternation(std::move(autoM), std::move(rightAutoM)); } return autoM; } public: RegexParser(const std::string str) : pos(str.begin()), end(str.end()) {} FiniteAutomaton parse() { return parseAlternation(); // 表达式开始于选择层级 } }; } // namespace namespace RegexToFA { FiniteAutomaton constructFromRegex(const std::string regexStr) { RegexParser parser(regexStr); return parser.parse(); } } // namespace RegexToFA这个解析器虽然简单但清晰地展示了递归下降的思想parseAlternation调用parseConcatenationparseConcatenation调用parseClosureparseClosure调用parseAtom形成了一个从低到高的优先级链条。隐式连接是通过在parseConcatenation中循环调用parseClosure来实现的。4. 可视化与调试将 NFA 打印为 DOT 语言构建出的 NFA 是一个复杂的内存图结构仅通过调试器查看指针很不直观。一个极佳的调试方法是将其输出为 Graphviz DOT 语言格式然后生成图片。我们在FiniteAutomaton类中添加一个方法。// 在 FiniteAutomaton 类声明中添加 std::string toDOT() const; // 实现 std::string FiniteAutomaton::toDOT() const { std::ostringstream oss; oss digraph NFA {\n; oss rankdirLR;\n; // 从左到右布局 oss node [shape circle];\n; std::setint visitedStates; std::queuestd::shared_ptrState q; q.push(startState); while (!q.empty()) { auto state q.front(); q.pop(); if (visitedStates.count(state-id)) continue; visitedStates.insert(state-id); // 节点形状接受状态为双圈 if (state-isAccepting) { oss state-id [shape doublecircle];\n; } else { oss state-id [shape circle];\n; } for (const auto edge : state-outgoingEdges) { auto target edge.target; std::string label; std::visit([label](auto arg) { using T std::decay_tdecltype(arg); if constexpr (std::is_same_vT, CharType) { label std::string(1, arg); // 对特殊字符进行转义方便DOT显示 if (arg \) label \\\; else if (arg \\) label \\\\; } else if constexpr (std::is_same_vT, CharRange) { label std::string([) arg.start - arg.end ]; } else if constexpr (std::is_same_vT, Epsilon) { label ε; } }, edge.condition); oss state-id - target-id [label\ label \];\n; if (!visitedStates.count(target-id)) { q.push(target); } } } oss }\n; return oss.str(); }将输出的字符串保存为.dot文件使用 Graphviz 的dot命令如dot -Tpng nfa.dot -o nfa.png即可生成直观的状态转移图这是验证构造是否正确的最有力工具。5. 实例运行与效果验证现在我们编写一个简单的main函数来测试整个流程。// main.cpp #include RegexToFA.h #include iostream #include fstream int main() { try { std::string regex; std::cout Enter a simple regex (support (), |, *, concat, e.g., a(b|c)*d): ; std::getline(std::cin, regex); auto nfa RegexToFA::constructFromRegex(regex); std::cout NFA constructed successfully!\n; std::cout Start State ID: nfa.startState-id std::endl; std::cout Accept State ID: nfa.acceptState-id std::endl; // 输出DOT格式 std::string dotStr nfa.toDOT(); std::ofstream dotFile(nfa.dot); dotFile dotStr; dotFile.close(); std::cout DOT file saved as nfa.dot. Use dot -Tpng nfa.dot -o nfa.png to visualize.\n; } catch (const std::exception e) { std::cerr Error: e.what() std::endl; return 1; } return 0; }测试示例 输入正则表达式a(b|c)*d程序运行后会生成nfa.dot文件。使用 Graphviz 渲染后你将得到一张清晰的 NFA 状态图。你可以看到从开始状态出发经过字符 ‘a’进入一个复杂的子图来处理(b|c)*最后通过字符 ‘d’ 到达接受状态。图中的 ε 边清晰地展示了 Thompson 构造法如何组合各个部分。5.1 常见问题与排查技巧实录在实际编码和测试过程中你几乎一定会遇到以下几个典型问题问题1内存访问冲突或泄漏现象程序在运行时崩溃Access Violation或退出后内存泄漏检测工具报警。原因NFA 是一个图状态之间循环引用非常普遍例如闭包构造中的循环 ε 边。如果使用原始指针需要极其复杂的手动生命周期管理即使使用shared_ptr循环引用也会导致内存泄漏因为引用计数无法归零。排查与解决使用弱指针打破循环对于已知可能形成循环的边如闭包中“接受状态→开始状态”的边可以考虑使用std::weak_ptr。但在自动机构造过程中判断哪些边会形成循环并不简单。更实用的方案将自动机视为整体管理。不要孤立地管理每个State。可以创建一个AutomatonBuilder或NFA类内部使用std::vectorstd::unique_ptrState集中管理所有状态的生命周期。转移边只存储状态的索引int或原始指针State*这些指针的生命周期由外部的vector保证。这是工业级编译器如LLVM中常见的做法它避免了智能指针在复杂图结构中的开销和循环引用问题。问题2解析复杂正则表达式时失败或结果错误现象对于a|b*c这样的表达式构造出的 NFA 逻辑不对。原因运算符优先级处理错误。正则表达式中闭包*优先级最高其次是连接隐式并置最后是选择|。我们的递归下降解析器通过函数调用层级parseAlternation - parseConcatenation - parseClosure - parseAtom天然实现了优先级。问题往往出在“连接”的识别上。排查技巧添加详细日志在每个parseXXX函数入口和退出时打印当前解析的字符位置和即将构造的内容。这能帮你看清解析器的“思考过程”。测试基础单元单独测试constructFromChar然后测试parseAtom对单个字符和括号的解析再逐步测试连接、选择、闭包。可视化对比对于有疑问的正则表达式手动绘制或查找标准的 Thompson NFA 图与你程序生成的 DOT 图进行对比差异点就是 bug 所在。问题3生成的DOT图无法渲染或布局混乱现象dot命令报错或生成的图片重叠严重。原因DOT 语言格式错误或节点/边属性设置不当。解决检查特殊字符转义我们已经在toDOT函数中对双引号和反斜杠进行了转义。但如果正则表达式包含\n,\t等需要更完善的转义逻辑。一个简单的方法是对于非打印字符用其ASCII码表示如label\\\n\。简化图形对于复杂的 NFA可以尝试在 DOT 文件中使用rankdirTB从上到下布局或者为关键状态子图添加subgraph和ranksame等布局提示。使用其他可视化工具如果 Graphviz 的dot布局不理想可以尝试neato或fdp等不同的布局引擎。问题4如何扩展以支持更多语法如,?,[a-z]扩展(一次或多次)可以转化为AA*即先构造 A 的自动机再构造 A 的闭包最后连接两者。也可以在constructClosure旁边实现一个constructPlus函数其逻辑与闭包类似只是不添加“从新开始状态直接到新接受状态”的 ε 边即不允许零次。扩展?(零次或一次)可以转化为(A|ε)即 A 和空串的选择。空串的自动机就是开始状态直接通过 ε 边连接到接受状态。扩展字符集[a-z]在parseAtom中识别[然后解析字符范围调用一个新的构造函数如constructFromRange生成一个状态其转移边条件为CharRange{a, z}。扩展转义字符在parseAtom的转义分支\中增加对\d,\w,\s等预定义字符集的处理将它们映射到对应的CharRange或一组Char条件。这个项目从理论到实践的桥梁就此搭建完成。它不仅仅是一个解析器更是一个理解计算模型和编译技术的窗口。你可以在此基础上继续实现 NFA 到 DFA 的转换子集构造法DFA 的最小化Hopcroft 算法最终得到一个可以高效匹配字符串的完整引擎。当你看到自己写的引擎成功匹配复杂模式时那种对底层原理的掌控感是调用任何现成 API 都无法比拟的。

相关推荐

【Bug已解决】Lab based Mz magnetometer Automation and sesnitivity calculations 解决方案
【Bug已解决】Lab based Mz magnetometer Automation and sesnitivity calculations 解决方案

【Bug已解决】Lab based Mz magnetometer Automation and sesnitivity calculations 解决方案原始需求线索:Lab based Mz magnetometer Automation and sesnitivity calculations(基于实验室的 Mz 磁强计自动化测量,以及灵敏度计算&#xff0… · 2026/9/21 4:44:00

Postman接口自动化测试全攻略:从基础到CI/CD集成
Postman接口自动化测试全攻略:从基础到CI/CD集成

1. 项目概述:为什么我们需要Postman接口自动化测试?如果你是一名后端开发、测试工程师,或者正在学习API开发,那么“接口测试”这个词你一定不陌生。过去,我们可能习惯于用浏览器插件、甚至直接写几行curl命令来手动测试… · 2026/8/12 12:03:23

C++并发编程锁机制全解析:从std::mutex到分布式锁实战
C++并发编程锁机制全解析:从std::mutex到分布式锁实战

1. 项目概述:为什么我们需要一本C锁的“百科全书”?在并发编程的世界里,锁(Lock)就像是十字路口的红绿灯,是协调多个执行流(线程或进程)有序访问共享资源、避免数据竞争(… · 2026/7/30 2:08:53

TypeScript 7 原生语言服务“转到实现”内存修复:从 O(K²) 爆炸到近似线性增长
TypeScript 7 原生语言服务“转到实现”内存修复:从 O(K²) 爆炸到近似线性增长

文档教程 【免费下载链接】typescript-book The Concise TypeScript Book: A Concise Guide to Effective Development in TypeScript. Free and Open Source. 项目地址: https://gitcode.com/gh_mirrors/typ/typescript-book 点击查看 免费下载 2026 年 7 月 30 日… · 2026/9/27 21:37:28

isomorphic-git readNote 详解:在 Node 与浏览器中读取 Git Note 注释内容
isomorphic-git readNote 详解:在 Node 与浏览器中读取 Git Note 注释内容

开发工具 【免费下载链接】isomorphic-git A pure JavaScript implementation of git for node and browsers! 项目地址: https://gitcode.com/gh_mirrors/is/isomorphic-git 点击查看 免费下载 readNote 是 isomorphic-git 提供的用于读取 Git Note(对… · 2026/9/27 21:37:28

Claude Code源码学习之上下文压缩(Compact):从触发条件到配置骨架
Claude Code源码学习之上下文压缩(Compact):从触发条件到配置骨架

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/27 21:37:28

安装wordpress注意什么意思最佳实践
安装wordpress注意什么意思最佳实践

新手装WordPress踩坑?3个配置细节对比评测帮你搞定服务器 域名服务器搞不懂?别慌,这确实是新手转行建站最头疼的坑。 很多人以为买个域名、租个服务器就能开工,结果一动手就卡壳。… · 2026/9/27 21:37:22

零后端硬改!LightRAG二次开发全局顶部导航,无缝对接自研第三方业务系统
零后端硬改!LightRAG二次开发全局顶部导航,无缝对接自研第三方业务系统

博主深耕AI本地部署、RAG工程化落地、开源框架二次开发运维实操,专注轻量化智能检索平台私有化适配改造,全程落地可复现,无冗余理论踩坑干货,适配政企内网自研业务全场景集成需求。 📌 写在前面:为什么一定要改 LightRAG 顶部 Header? 很多政企、园区、事业单位技术团… · 2026/9/27 21:37:22

树和二叉树的应用(哈喜老师)
树和二叉树的应用(哈喜老师)

1.哈夫曼树(也称为赫夫曼树)(重点) 1.1:一些与树相关的概念1.2:哈夫曼树的概念1.3:哈夫曼树的构造1.4:哈夫曼树的性质习题1(易)习题2(题目简单,但是问法很新颖)习题3(易错… · 2026/9/27 21:37:22

MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现

简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01

汕头网站建设制作厂家避坑指南:5大注意事项救急
汕头网站建设制作厂家避坑指南:5大注意事项救急

汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01

多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习

简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01

MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现
MATLAB雷达信号脉冲压缩仿真:LFM线性调频、匹配滤波与距离分辨率实现

简介:这套Matlab仿真工具完整呈现雷达信号脉冲压缩过程,从线性调频(LFM)信号生成、目标回波仿真到匹配滤波压缩处理均有可运行代码支撑,面向电子信息工程、计算机、数学等专业学生,适用于课程设计、期末大作… · 2026/9/27 0:00:01

汕头网站建设制作厂家避坑指南:5大注意事项救急
汕头网站建设制作厂家避坑指南:5大注意事项救急

汕头网站建设制作厂家避坑指南:5大注意事项救急 改个需求建站公司拖一周,这种憋屈事我见得太多了。 很多汕头老板找本地建站团队,签合同前看着方案挺美,一上线就变脸。 今天不聊虚的,直接拆解找 汕头网站建设制作厂家 时的5个核心 注意事项… · 2026/9/27 0:00:01

多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习
多模态虚假新闻检测实战:BERT+ResNet双塔与对比学习

简介:基于PyTorch的多模态虚假新闻检测项目完整代码包,面向自然语言处理与计算机视觉交叉方向的开发者、科研人员及毕业设计选题者,解决社交媒体中文本与图像联合识别虚假新闻的问题。系统以BERT预训练模型提取文本语义特征,以Res… · 2026/9/27 0:00:01

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码