第 4 章 · 02 NFA 与 DFA:两种自动机的取舍 本节摘要:上一节建立了「正则 = 自动机」,这一节讲自动机的两种类型:DFA(确定有限自动机)与 NFA(非确定有限自动机)。两者能力相同(能识别的语言一样),但性格迥异:DFA 匹配快但构造可能状态爆炸;NFA 构造简单但匹配需要处理「非确定性」。这个取舍,是正则引擎设计(以及下一节回溯 vs DFA 的路线选择)的核心。理解 NFA 与 DFA 的差别,你才能看懂为什么不同正则引擎表现不同。 内容来源:基于正则理论整理的导读。 学习目标 阅读完本节,你应当能够: 说清 DFA(确定)与 NFA(非确定)的定义差别。 解释「非确定性」的两层含义:同一输入多条出边、ε 转移(空输入跳转)。
本节摘要:上一节建立了「正则 = 自动机」,这一节讲自动机的两种类型:DFA(确定有限自动机)与 NFA(非确定有限自动机)。两者能力相同(能识别的语言一样),但性格迥异:DFA 匹配快但构造可能状态爆炸;NFA 构造简单但匹配需要处理「非确定性」。这个取舍,是正则引擎设计(以及下一节回溯 vs DFA 的路线选择)的核心。理解 NFA 与 DFA 的差别,你才能看懂为什么不同正则引擎表现不同。
内容来源:基于正则理论整理的导读。
阅读完本节,你应当能够:
正则引擎实现的两条路线——DFA 式与回溯式(下一节)——直接对应这里讲的 DFA 与 NFA 两种自动机。理解这一节的取舍,你就理解了:
grep 默认用 DFA(快)但 Python re 用回溯(支持反向引用)。DFA 的规则严格:在任何状态,对任何输入字符,有且仅有一个确定的下一状态。
DFA 示例(识别 ab): a b (0) ──────► (1) ──────► ((2))
DFA 的好处是匹配快:每读一个字符只做一次状态查表,O(n) 时间(n 是输入长度),与正则复杂度无关。坏处是构造可能爆炸:某些正则对应的 DFA 状态数会指数级膨胀。
NFA 放宽了两个限制:
NFA 示例(识别 a|b): a ┌─────► (1) ──► ((2)) (0) └─────► (3) ──► ((4)) b
状态 0 读 a 可去 1,读 b 可去 3。这种「非确定」怎么执行?两种思路:
NFA 的好处是构造简单(Thompson 构造直接产出 NFA,下一节讲),坏处是匹配要处理非确定,最坏情况(回溯)会慢。
定理:NFA 与 DFA 识别的语言完全相同——任何 NFA 都能转成等价的 DFA(子集构造法),反之亦然。差别只在「构造成本」与「匹配成本」的权衡:
| DFA | NFA | |
|---|---|---|
| 构造 | 可能状态爆炸 | 简单(Thompson 构造) |
| 匹配 | 快(O(n)) | 视策略,回溯可能慢 |
| 支持反向引用 | 难 | 可(回溯式) |
用 a|b(a 或 b)这个正则,画 DFA 和 NFA 各一个,对比:
你会直观感到「NFA 更好画」——这正是 Thompson 构造用 NFA 的原因。
ab、a*、a|b),体会构造难度差异。下一节是全章最核心、最美的算法——Thompson 构造,把任意正则系统编译成 NFA。