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

别再死记硬背NFA转DFA的算法了!用Python手写一个转换器,理解更透彻

用Python实现NFA到DFA转换从理论到代码的实战指南第一次接触NFA转DFA算法时我被那些抽象的状态集合和ε闭包概念弄得晕头转向。直到有一天我决定用Python把这些理论变成可运行的代码一切突然变得清晰起来。这篇文章将带你用不到200行代码构建一个完整的NFA到DFA转换器让你在动手实践中真正理解这个经典算法。1. 理解NFA和DFA的核心差异在开始编码前我们需要明确几个关键概念NFA非确定性有限自动机给定状态和输入符号可能有多个转移选择甚至允许ε空转移DFA确定性有限自动机每个状态对每个输入符号都有且只有一个确定的转移两者在表达能力上是等价的但DFA更容易用程序实现。这就是为什么我们需要掌握NFA转DFA的技术。关键区别对比表特性NFADFA状态转移多值包括空转移单值实现复杂度理论描述简单实现效率高状态表示单个状态状态集合接受条件存在至少一条接受路径唯一终止状态2. 构建NFA的数据结构我们先定义NFA的五个基本组件class NFA: def __init__(self): self.states set() # 状态集合 self.alphabet set() # 输入字母表 self.transitions {} # 转移函数 self.start_state None # 初始状态 self.accept_states set() # 接受状态集让我们用一个具体例子来初始化NFA。考虑能识别字符串以ab结尾的NFA# 创建NFA实例 nfa NFA() nfa.states {0, 1, 2} nfa.alphabet {a, b} nfa.start_state 0 nfa.accept_states {2} # 定义转移函数 nfa.transitions { 0: {a: {0, 1}, b: {0}}, 1: {b: {2}}, 2: {} # 无转移 }这个NFA的特点是状态0遇到a可以保持在0或转移到1遇到b则保持在0状态1遇到b转移到接受状态2。3. 实现ε闭包计算ε闭包是NFA转DFA的核心操作表示从某状态通过ε转移能到达的所有状态集合。虽然我们的例子没有ε转移但为了完整性我们仍实现这个功能def epsilon_closure(nfa, states): 计算给定状态集的ε闭包 closure set(states) stack list(states) while stack: state stack.pop() # 检查是否有ε转移用表示 if in nfa.transitions.get(state, {}): for next_state in nfa.transitions[state][]: if next_state not in closure: closure.add(next_state) stack.append(next_state) return frozenset(closure)注意在实际编码中我们使用空字符串表示ε转移。虽然当前示例不需要但完整的实现应该包含这个功能。4. 子集构造法实现现在来到最关键的步骤——将NFA转换为DFA。子集构造法的核心思想是将NFA的状态集合作为DFA的单个状态。def nfa_to_dfa(nfa): dfa DFA() dfa.alphabet nfa.alphabet # 计算初始状态NFA的初始状态及其ε闭包 initial_state epsilon_closure(nfa, {nfa.start_state}) dfa.start_state initial_state # 初始化工作队列和已处理状态集合 unprocessed_states [initial_state] processed_states set() # 如果初始状态包含任何接受状态则也是DFA的接受状态 if any(state in nfa.accept_states for state in initial_state): dfa.accept_states.add(initial_state) while unprocessed_states: current_state unprocessed_states.pop() if current_state in processed_states: continue processed_states.add(current_state) for symbol in nfa.alphabet: # 计算move(current_state, symbol) next_states set() for state in current_state: if symbol in nfa.transitions.get(state, {}): next_states.update(nfa.transitions[state][symbol]) if not next_states: continue # 计算ε闭包 next_state epsilon_closure(nfa, next_states) # 添加到转移表 if current_state not in dfa.transitions: dfa.transitions[current_state] {} dfa.transitions[current_state][symbol] next_state # 如果包含接受状态则添加到DFA的接受状态集 if any(state in nfa.accept_states for state in next_state): dfa.accept_states.add(next_state) # 将新状态加入待处理队列 if next_state not in processed_states: unprocessed_states.append(next_state) return dfa5. 可视化DFA转换结果为了直观展示转换结果我们可以实现一个简单的可视化函数def visualize_dfa(dfa): print(DFA状态转移表) print(状态\t\t \t.join(dfa.alphabet)) for state in dfa.transitions: state_str { ,.join(map(str, state)) } transitions [] for symbol in dfa.alphabet: next_state dfa.transitions[state].get(symbol, set()) next_str { ,.join(map(str, next_state)) } if next_state else ∅ transitions.append(next_str) print(f{state_str}\t \t.join(transitions)) print(\n接受状态) for state in dfa.accept_states: print({ ,.join(map(str, state)) })对我们的示例NFA运行转换后输出如下DFA状态转移表 状态 a b {0} {0,1} {0} {0,1} {0,1} {0,2} {0,2} {0,1} {0} 接受状态 {0,2}6. 测试与验证为了确保我们的转换器工作正常让我们编写测试用例# 测试NFA是否接受特定字符串 def test_nfa_acceptance(nfa, input_string): current_states epsilon_closure(nfa, {nfa.start_state}) for symbol in input_string: next_states set() for state in current_states: if symbol in nfa.transitions.get(state, {}): next_states.update(nfa.transitions[state][symbol]) if not next_states: return False current_states epsilon_closure(nfa, next_states) return any(state in nfa.accept_states for state in current_states) # 测试DFA是否接受特定字符串 def test_dfa_acceptance(dfa, input_string): current_state dfa.start_state for symbol in input_string: if symbol not in dfa.transitions.get(current_state, {}): return False current_state dfa.transitions[current_state][symbol] return current_state in dfa.accept_states # 测试用例 test_cases [ab, aab, abab, b, aa, abb] print(NFA和DFA接受性测试结果) print(字符串\tNFA\tDFA) for test_str in test_cases: nfa_result test_nfa_acceptance(nfa, test_str) dfa_result test_dfa_acceptance(dfa, test_str) print(f{test_str}\t{nfa_result}\t{dfa_result})输出结果应该显示NFA和DFA对所有测试字符串的判断一致验证了我们转换的正确性。7. 性能优化与实践技巧在实际应用中我们还可以对基础算法进行优化状态最小化转换后的DFA可能含有等价状态可以使用Hopcroft算法进一步优化惰性计算对于大型自动机可以只计算实际需要的状态转移记忆化存储缓存已计算的ε闭包结果一个实用的状态最小化实现示例def minimize_dfa(dfa): # 初始化划分接受状态和非接受状态 partitions [frozenset(dfa.accept_states), frozenset(state for state in dfa.transitions if state not in dfa.accept_states)] changed True while changed: changed False new_partitions [] for partition in partitions: # 按转移行为细分划分 split_dict {} for state in partition: key tuple() for symbol in dfa.alphabet: next_state dfa.transitions[state].get(symbol, frozenset()) # 找出next_state所属的划分索引 for i, p in enumerate(partitions): if next_state in p: key (i,) break if key not in split_dict: split_dict[key] set() split_dict[key].add(state) # 如果有细分则标记变化 if len(split_dict) 1: changed True new_partitions.extend(frozenset(s) for s in split_dict.values()) else: new_partitions.append(partition) partitions new_partitions # 构建最小化DFA minimized_dfa DFA() minimized_dfa.alphabet dfa.alphabet # 创建新状态到划分的映射 state_to_partition {} for partition in partitions: for state in partition: state_to_partition[state] partition # 设置初始状态 minimized_dfa.start_state state_to_partition[dfa.start_state] # 设置接受状态 minimized_dfa.accept_states set() for state in dfa.accept_states: minimized_dfa.accept_states.add(state_to_partition[state]) # 构建转移函数 minimized_dfa.transitions {} for partition in partitions: representative next(iter(partition)) minimized_dfa.transitions[partition] {} for symbol in dfa.alphabet: if representative in dfa.transitions and symbol in dfa.transitions[representative]: next_state dfa.transitions[representative][symbol] minimized_dfa.transitions[partition][symbol] state_to_partition[next_state] return minimized_dfa8. 扩展应用与进阶思考掌握了NFA到DFA的转换后你可以进一步探索正则表达式引擎基于NFA/DFA构建自己的正则表达式解析器词法分析器编译原理中lexer的核心就是有限自动机模式匹配算法许多字符串搜索算法都基于自动机理论一个有趣的应用是将正则表达式转换为NFA再转为DFAdef regex_to_nfa(regex): 将简单正则表达式转换为NFA基础实现 nfa NFA() state_counter 0 # 这里简化处理实际实现需要解析正则表达式 # 仅处理形如a*b这样的简单模式 if regex a*b: nfa.states {0, 1, 2} nfa.alphabet {a, b} nfa.start_state 0 nfa.accept_states {2} nfa.transitions { 0: {a: {0, 1}, b: {1}}, 1: {b: {2}} } return nfa通过这个完整的实现过程你会发现那些在课本上看起来抽象的概念一旦转化为代码就会变得具体而清晰。这就是为什么我认为学习计算理论最好的方式就是动手实现它。

相关文章:

别再死记硬背NFA转DFA的算法了!用Python手写一个转换器,理解更透彻

用Python实现NFA到DFA转换:从理论到代码的实战指南 第一次接触NFA转DFA算法时,我被那些抽象的状态集合和ε闭包概念弄得晕头转向。直到有一天,我决定用Python把这些理论变成可运行的代码,一切突然变得清晰起来。这篇文章将带你用不…...

别再只用IoU了!目标检测模型调参时,如何根据你的数据集选择最合适的损失函数?

目标检测损失函数实战指南:如何为你的数据集定制最优方案 在目标检测任务中,损失函数的选择往往决定了模型的最终表现。面对琳琅满目的IoU变体——从基础的IoU到GIOU、DIOU、CIOU,再到最新的EIOU和SIOU,开发者们常常陷入选择困难。…...

新谈设计模式 Chapter 18 — 观察者模式 Observer

Chapter 18 — 观察者模式 Observer灵魂速记:微信公众号——发了文章自动推送给所有关注者,取关了就收不到。秒懂类比 你关注了一个公众号。公众号发文章时,不需要知道你是谁,只需要把文章推给所有关注者。你想取关?取…...

别再死记硬背了!用一张图+三个比喻,彻底搞懂波导里的TE、TM、TEM模式

用生活化比喻破解波导模式:TE、TM、TEM的视觉化理解指南 电磁波在波导中的传播模式,是许多工程师和学生头疼的"拦路虎"。传统教材中充斥着复杂的数学公式和抽象定义,让人望而生畏。但理解这些概念其实可以像看一场足球赛一样直观—…...

深入TelephonyProvider:Android APN配置从xml到SQLite的完整加载与更新机制

Android APN配置全链路解析:从XML到SQLite的深度实现 在移动通信领域,APN(接入点名称)配置的正确性直接决定了设备能否正常接入运营商网络。作为Android系统工程师,深入理解TelephonyProvider如何管理APN配置不仅有助于…...

告别Pickle风险!用Hugging Face的safetensors安全保存你的PyTorch模型权重

告别Pickle风险:用Hugging Face的safetensors实现PyTorch模型安全部署 当你在GitHub上发现一个有趣的PyTorch模型,迫不及待想试试效果时,有没有想过那个.pth文件里可能藏着什么?去年某知名开源项目就曾发生过恶意代码通过模型权重…...

用Python玩转奥比中光Gemini Pro:从开箱到实时获取深度图与彩色图的保姆级教程

用Python玩转奥比中光Gemini Pro:从开箱到实时获取深度图与彩色图的保姆级教程 刚拿到奥比中光Gemini Pro相机的开发者们,是否迫不及待想看到它强大的深度视觉能力?本文将带你从零开始,一步步完成环境搭建、设备连接、代码调试&am…...

别再纠结用哪个库了!Python量化实战:MyTT、TA-Lib、Pandas TA三大指标库横向评测(附避坑指南)

Python量化实战:三大指标库MyTT、TA-Lib与Pandas TA的深度选型指南 当你在凌晨三点盯着屏幕,反复调试不同库的MACD指标输出时,是否想过——为什么同样的算法会有不同结果?这可能是每个量化开发者都会经历的"黑暗时刻"。…...

采取一个系统化方法来分析和处理数据_(充电桩local信息、时间、车辆状态、SOC、电流、电压等信息)之城市电动汽车充电桩数据集 数据预处理、特征工程、探索性数据分析

采取一个系统化方法来分析和处理数据_(充电桩local信息、时间、车辆状态、SOC、电流、电压等信息)之城市电动汽车充电桩数据集 数据预处理、特征工程、探索性数据分析 文章目录以下文字及代码仅供参考。1. 数据理解与准备加载原始数据合并数据2. 数据清理与特征工程数据清洗特征…...

Rusted PackFile Manager:现代化架构重构与高性能游戏模组开发技术指南

Rusted PackFile Manager:现代化架构重构与高性能游戏模组开发技术指南 【免费下载链接】rpfm Rusted PackFile Manager (RPFM) is a... reimplementation in Rust and Qt5 of PackFile Manager (PFM), one of the best modding tools for Total War Games. 项目地…...

从‘背答案’到‘真理解’:用数据增强和正则化给你的CV模型‘减肥’

从‘背答案’到‘真理解’:用数据增强和正则化给你的CV模型‘减肥’ 当你第一次训练计算机视觉模型时,可能会遇到一个令人沮丧的现象:模型在训练集上表现近乎完美,但在从未见过的测试数据上却一塌糊涂。这种"高分低能"的…...

如何使用YOLOv8训练变电站电力设备缺陷数据集 共6004张图像 有txt和yaml两种格式 表计读数异常、表计外壳破损、异物鸟巢、空中漂浮物、表盘模糊、表盘破损、绝缘子破裂、地面油污、硅胶桶变色

如何使用YOLOv8训练变电站电力设备缺陷数据集 共6004张图像 有txt和yaml两种格式 表计读数异常、表计外壳破损、异物鸟巢、空中漂浮物、表盘模糊、表盘破损、绝缘子破裂、地面油污、硅胶桶变色 添加图片注释,不超过 140 字(可选) 添加图片注释…...

ROS机器人仿真避坑:Gazebo差速插件与robot_state_publisher的TF冲突解决(附.xacro配置)

ROS机器人仿真中的TF冲突:Gazebo差速插件与robot_state_publisher的协同优化 当你在Rviz中看到机器人模型不断抖动,终端窗口不断刷出TF_REPEATED_DATA警告时,这通常意味着你的系统中存在多个TF数据发布源。这种问题在ROS机器人仿真中尤为常见…...

LilyGO T-PicoC3双MCU开发板解析与IoT应用

1. LilyGO T-PicoC3开发板深度解析在嵌入式开发领域,我们经常面临一个经典难题:如何在一块板卡上同时获得强大的本地计算能力和稳定的无线连接功能?LilyGO T-PicoC3开发板给出了一个颇具创意的解决方案——将树莓派RP2040与ESP32-C3两颗明星级…...

Qt实战:5分钟搞定QTableWidget列宽自适应(附完整代码)

Qt实战:5分钟掌握QTableWidget列宽自适应技巧 刚接触Qt开发时,表格控件的布局问题总是让人头疼——要么列宽太窄显示不全内容,要么留出大片空白显得不专业。作为Qt中最常用的数据展示组件之一,QTableWidget的列宽自适应其实只需要…...

百度网盘限速破解终极指南:使用baidu-wangpan-parse实现满速下载

百度网盘限速破解终极指南:使用baidu-wangpan-parse实现满速下载 【免费下载链接】baidu-wangpan-parse 获取百度网盘分享文件的下载地址 项目地址: https://gitcode.com/gh_mirrors/ba/baidu-wangpan-parse 你是否曾为百度网盘那令人抓狂的下载速度而烦恼&a…...

从“零拷贝”到“写合并”:深入CUDA锁页内存的三种高级用法(附代码避坑)

从“零拷贝”到“写合并”:深入CUDA锁页内存的三种高级用法(附代码避坑) 在GPU加速计算的世界里,内存管理往往是性能优化的关键战场。当开发者已经掌握了CUDA基础内存操作后,锁页内存(Page-Locked Memory&a…...

别再被‘HDR400’忽悠了!手把手教你读懂VESA DisplayHDR认证,买显示器不踩坑

别再被‘HDR400’忽悠了!手把手教你读懂VESA DisplayHDR认证,买显示器不踩坑 走进任何一家电子产品卖场或打开电商平台,显示器的宣传页上总能看到"HDR400"、"HDR600"这样的标签。这些看似专业的认证标识背后,…...

C语言学习笔记 - 4.C概述 - C的特点

本笔记基于郝斌-C语言自学入门教程整理,配套参考教材谭浩强《C程序设计(第五版)》第1章1.3节,适配VSCode C/C开发环境,核心梳理C语言的核心优势与固有缺陷,帮助建立对C语言的完整认知。一、C语言的核心优点C语言的核心竞争力集中在…...

5分钟上手UK Biobank RAP:生物医学研究的云端分析终极指南

5分钟上手UK Biobank RAP:生物医学研究的云端分析终极指南 【免费下载链接】UKB_RAP Access share reviewed code & Jupyter Notebooks for use on the UK Biobank (UKBB) Research Application Platform. Includes resources from DNAnexus webinars, online t…...

手把手教你用Windows自带工具无损转换MBR到GPT(附BIOS/UEFI切换指南)

Windows系统盘无损转换MBR到GPT全流程实战指南 当你准备升级到Windows 11或使用超过2TB的大容量硬盘时,传统的MBR分区表可能成为瓶颈。不同于第三方工具可能带来的兼容性风险,Windows内置的MBR2GPT工具提供了一条安全可靠的转换路径。我曾帮助数十位同事…...

Windows窗口置顶终极指南:用PinWin告别频繁切换的烦恼![特殊字符]

Windows窗口置顶终极指南:用PinWin告别频繁切换的烦恼!🎯 【免费下载链接】PinWin Pin any window to be always on top of the screen 项目地址: https://gitcode.com/gh_mirrors/pin/PinWin 你是否曾经在写代码时频繁切换窗口查看文…...

告别同步焦虑:我的Obsidian+坚果云+FolderSync多端同步工作流搭建心得与备份策略

告别同步焦虑:我的Obsidian坚果云FolderSync多端同步工作流搭建心得与备份策略 作为一名长期依赖数字笔记的知识工作者,我深知一套稳定可靠的同步系统有多重要。三年前一次硬盘故障导致我丢失了整整两个月的项目笔记后,我开始系统性研究如何构…...

别再搞混了!UE5角色移动时,GetActorForwardVector和GetControlRotation到底该用哪个?

UE5角色移动方向选择指南:GetActorForwardVector与GetControlRotation的实战解析 在虚幻引擎5的角色移动开发中,方向控制是最基础却最容易出错的环节之一。许多开发者都经历过角色莫名转圈、移动抖动或朝向异常的困扰——这些问题往往源于对GetActorForw…...

别再手动洗数据了!用Datatrove Pipeline把FastText分类和关键词过滤自动化

从零构建自动化数据清洗流水线:基于Datatrove与FastText的工程实践 在机器学习项目的生命周期中,数据清洗往往占据70%以上的时间成本。传统的手工处理方式不仅效率低下,更难以应对TB级数据的规模化挑战。本文将分享如何利用Datatrove框架与Fa…...

Substance Painter 9 与 Unity 2019.4 材质效果同步实战:从光源、相机到环境球的全流程对齐

Substance Painter与Unity材质效果同步全流程指南:从理论到实践 在3D美术创作流程中,Substance Painter与Unity的材质效果同步一直是困扰美术师的难题。当你在Substance Painter中精心雕琢的材质导入Unity后"变了味",那种挫败感足以…...

避坑指南:ESP32 MicroPython读写SD卡,为什么你的代码总报错?

ESP32 MicroPython SD卡读写避坑实战:从报错到稳定运行的深度解析 当你在ESP32上尝试用MicroPython操作SD卡时,是否遇到过这些令人抓狂的场景?明明按照教程连接了硬件,代码却抛出OSError: no SD card;或者文件系统挂载…...

如何高效提取SWF资源:JPEXS Free Flash Decompiler终极指南

如何高效提取SWF资源:JPEXS Free Flash Decompiler终极指南 【免费下载链接】jpexs-decompiler JPEXS Free Flash Decompiler 项目地址: https://gitcode.com/gh_mirrors/jp/jpexs-decompiler 还在为无法从SWF文件中提取图像和音频而烦恼吗?面对那…...

LK光流法在无人机视觉避障中的实战:结合金字塔与反向光流提升跟踪鲁棒性

LK光流法在无人机视觉避障中的实战:结合金字塔与反向光流提升跟踪鲁棒性 当四旋翼无人机以8米/秒的速度穿越狭窄巷道时,传统基于GPS的导航系统会因信号遮挡完全失效。这时,视觉系统成了唯一的"眼睛",而LK光流法正是这双…...

三步打造个人AI记忆库:微信聊天记录永久保存与智能分析终极指南

三步打造个人AI记忆库:微信聊天记录永久保存与智能分析终极指南 【免费下载链接】WeChatMsg 提取微信聊天记录,将其导出成HTML、Word、CSV文档永久保存,对聊天记录进行分析生成年度聊天报告 项目地址: https://gitcode.com/GitHub_Trending…...