DFA 与 NFA 引擎:它们的功能和限制有何不同?

Posted

技术标签:

【中文标题】DFA 与 NFA 引擎:它们的功能和限制有何不同?【英文标题】:DFA vs NFA engines: What is the difference in their capabilities and limitations? 【发布时间】:2011-04-28 01:34:02 【问题描述】:

我正在寻找关于 DFA 与 NFA 引擎之间差异的非技术解释,基于它们的功能和限制。

【问题讨论】:

en.wikipedia.org/wiki/Deterministic_finite-state_machine @SilentGhost 我知道它在数学上并不繁重,但那篇文章取决于一个知道他们没有解释的所有数学符号的人。像许多***文章一样,它是由非常了解该主题的人撰写的,从初学者的角度看不到它,让我们面对现实,谁会阅读该文章最多。 This is by far the best explanation I have seen of DFAs vs NFAs。 TL;DR、DFA 和 NFA 具有相同的功能和限制,但使用 NFA 的一些实现性能较差。 【参考方案1】:

确定性有限自动机 (DFA) 和非确定性有限自动机 (NFA) 具有完全相同的功能和限制。唯一的区别是符号方便。

有限自动机是具有状态并读取输入的处理器,每个输入字符都可能将其设置为另一种状态。例如,一个状态可能是“刚刚连续读两个 C”或“正在开始一个单词”。这些通常用于快速扫描文本以查找模式,例如对源代码进行词法扫描以将其转换为标记。

确定性有限自动机一次处于一种状态,这是可以实现的。非确定性有限自动机一次可以处于多个状态:例如,在标识符可以以数字开头的语言中,可能存在“读取数字”状态和“读取标识符”状态,以及NFA 可能在读取以“123”开头的内容时同时出现在两者中。实际应用哪种状态取决于它是否在单词结尾之前遇到了非数字的东西。

现在,我们可以将“读取数字或标识符”表示为状态本身,突然我们不需要 NFA。如果我们将 NFA 中的状态组合表示为状态本身,那么我们得到的 DFA 的状态比 NFA 多得多,但它做同样的事情。

这是一个更容易阅读或写作或处理的问题。 DFA 本身更容易理解,但 NFA 通常更小。

【讨论】:

好的,现在已删除的答案将 NFA 与 DFA 混淆了。我以前见过人们这样做,显然这要归功于一本有用的书,或者无论如何这是这样声明的:fanf.livejournal.com/37166.html【参考方案2】:

这是来自 Microsoft 的非技术性回答:

DFA 引擎以线性时间运行,因为它们不需要回溯(因此它们不会两次测试同一个字符)。他们还可以保证匹配尽可能长的字符串。但是,由于 DFA 引擎只包含有限状态,它无法匹配带有反向引用的模式,并且由于它不构造显式扩展,因此无法捕获子表达式。

传统的 NFA 引擎运行所谓的“贪婪”匹配回溯算法,以特定顺序测试正则表达式的所有可能扩展并接受第一个匹配。因为传统 NFA 为成功匹配构建了正则表达式的特定扩展,所以它可以捕获子表达式匹配和匹配反向引用。然而,由于传统的 NFA 回溯,如果状态是通过不同的路径到达的,它可以多次访问完全相同的状态。因此,在最坏的情况下,它可能会以指数方式缓慢运行。因为传统的 NFA 接受它找到的第一个匹配项,所以它也可以让其他(可能更长的)匹配项未被发现。

POSIX NFA 引擎与传统 NFA 引擎类似,不同之处在于它们会继续回溯,直到可以保证找到可能的最长匹配。因此,POSIX NFA 引擎比传统 NFA 引擎慢,并且在使用 POSIX NFA 时,您不能通过更改回溯搜索的顺序来偏爱较短的匹配而不是较长的匹配。

传统的 NFA 引擎受到程序员的青睐,因为它们比 DFA 或 POSIX NFA 引擎更具表现力。尽管在最坏的情况下它们可能运行缓慢,但您可以使用减少歧义和限制回溯的模式引导它们在线性或多项式时间内找到匹配项。

[http://msdn.microsoft.com/en-us/library/0yzc2yb0.aspx]

【讨论】:

MSDN 文章颇具误导性; NFA 和 DFA 同样强大。 NFA 算法不需要回溯(具有最坏情况的指数行为)。需要回溯的原因是因为“正则表达式”比正则语言更强大(例如反向引用),因此它们不能由规范的 NFA/DFA 建模。不使用回溯的良好实现的 NFA 算法示例:swtch.com/~rsc/regexp/regexp1.html 关于灾难性回溯的话题:stackstatus.net/post/147710624694/… 如果您坚持标准定义,MS 文章实际上是错误的:NFA 根本不会回溯。如果你从 NFA 开始,某些回溯自动机更容易实现,也许这篇文章指的是这样的实现。【参考方案3】:

一个简单的、非技术性的解释,转述自 Jeffrey Friedl 的书 Mastering Regular Expressions。

警告

虽然这本书通常被认为是“正则表达式圣经”,但对于此处对 DFA 和 NFA 的区分是否真的正确,似乎存在一些争议。我不是计算机科学家,我不理解“常规”表达式背后的大部分理论,无论是否具有确定性。争议开始后,我因此删除了这个答案,但此后在 cmets 中被引用到其他答案。我很想进一步讨论这个问题——弗里德尔真的错了吗?还是我弄错了弗里德尔(但我昨天晚上重读了那一章,就像我记得的一样……)?

编辑:看来弗里德尔和我确实错了。请在下方查看 Eamon 的优秀 cmets。


原答案:

DFA 引擎逐个字符地遍历输入字符串,并尝试(并记住)此时正则表达式可以匹配的所有可能方式。如果它到达字符串的末尾,则声明成功。

想象一下字符串AAB 和正则表达式A*AB。现在我们逐个字母地遍历我们的字符串。

    A

    第一个分支:可以匹配A*。 第二个分支:可以通过忽略A*(允许零重复)并在正则表达式中使用第二个A 来匹配。

    A:

    第一个分支:可以通过扩展A*来匹配。 第二个分支:B 无法匹配。第二个分支失败。但是: 第三个分支:可以通过不扩展 A* 而使用第二个 A 来匹配。

    B:

    第一个分支:无法通过扩展 A* 或在正则表达式中移动到下一个标记 A 来匹配。第一个分支失败。 第三个分支:可以匹配。万岁!

DFA 引擎从不回溯字符串。


NFA 引擎 逐个标记地遍历 regex 标记,并尝试字符串上所有可能的排列,必要时回溯。如果它到达正则表达式的末尾,则声明成功。

想象一下与以前相同的字符串和相同的正则表达式。我们现在逐个令牌地遍历我们的正则表达式令牌:

    A*:匹配AA。记住回溯位置 0(字符串的开头)和 1。 A:不匹配。但是我们有一个回溯位置,我们可以返回并重试。正则表达式引擎后退一个字符。现在A 匹配。 B:匹配。正则表达式结束(有一个回溯位置备用)。万岁!

【讨论】:

这个答案是不准确的——灾难性的回溯与整个 NFA/DFA 的区别是正交的。您所描述的 DFA 实际上是 NFA(使用典型的状态叠加)- DFA 仅处于一个状态,因此是“确定性的”,而 NFA 可能处于多个状态,因此是非确定性的。 从某种意义上说,这只是术语。话虽如此,DFA 是确定性的(顾名思义),而 NFA 是非确定性的(同样,顾名思义)。这有一个非常直截了当的原因:DFA 总是处于一种状态,当呈现一个角色时,总是有一个唯一的(确定的)下一个状态,该角色始终对应。所以,你的第一个解释是一个很好的正则表达式算法,但它不是一个 DFA - 显然,正如你所描述的,可以有多个选项,在字符串结束之前你永远不知道哪个是“最好的”。 您的第二种算法标记为 NFA 引擎 确实是一种可能的 NFA 实现。它以不同的方式解决了您的第一个(也是 NFA)算法的相同歧义:即只需选择一个选项并根据需要回溯。因此,它确实是一个 NFA,但它不是唯一可能的 NFA,正如您的第一个方法所展示的那样:它以不同的方式处理相同的不确定性。我想您可以将其称为回溯 NFA 引擎,以区分两者。 最后,在 any 有限状态自动机中,顾名思义,状态是 finite - 更具体地说,在嵌入任何相关信息进入状态,该元组仍然需要有有限数量的选项。 that 意味着严格来说,兼容 perl 的引擎并不是真正的 any 类型的 FSA,既不是 DFA 也不是 NFA:毕竟,您可以包含任意长度的反向引用,并且有 无限 个任意长度的字符串。 区别对性能至关重要,因为无限的状态空间意味着您无法预编译 NFA,也无法使用第一种算法有效地执行它。在一般情况下,当面对正则表达式(并且您在实践中遇到这些)时,回溯会中断,这会导致灾难性的回溯。【参考方案4】:

正如他们的名字所说,NFA 和 DFA 都是有限自动机。

两者都可以表示为开始状态、成功(或“接受”)状态(或成功状态集)以及列出转换的状态表。

在 DFA 的状态表中,每个 <state₀, input> 键将转换为一个且只有一个 state₁

在 NFA 的状态表中,每个 <state₀, input> 将转换到 一组 状态。

当您使用 DFA 时,将其重置为开始状态,为其提供一系列输入符号,您将确切知道它处于何种结束状态以及是否为成功状态。

但是,当您采用 NFA 时,它会针对每个输入符号查找一组可能的结果状态,并(理论上)随机“非确定性地”选择其中一个。如果存在导致该输入字符串的其中一个成功状态的随机选择序列,则称该 NFA 对该字符串成功。换句话说,您应该假装它总是神奇地选择正确的。

计算中的一个早期问题是,由于这种魔力,NFA 是否比 DFA 更强大,而答案结果是,因为任何 NFA 都可以转换为等效的 DFA。 它们的能力和局限性完全相同。

对于那些想知道真实的、非神奇的 NFA 引擎如何“神奇地”为给定符号选择正确的后继状态的人,this page 描述了两种常见的方法。

【讨论】:

“然后说 DFA 对那个字符串成功”是否意味着改为“NFA”?【参考方案5】:

我发现 Jan Goyvaerts 在Regular Expressions, The Complete Tutorial 中给出的解释是最有用的。请参阅此 PDF 的第 7 页:

https://www.princeton.edu/~mlovett/reference/Regular-Expressions.pdf

在第 7 页上提出的其他要点中,有两种正则表达式引擎:文本导向引擎和正则表达式导向引擎。 Jeffrey Friedl 分别称它们为 DFA 和 NFA 引擎。 ...某些非常有用的特性,例如惰性量词和反向引用,只能在正则表达式导向的引擎中实现。

【讨论】:

我相信引用是现在 Regular-Expressions.info 的基础。这是同一位作者,我认得其中的一些措辞。

以上是关于DFA 与 NFA 引擎:它们的功能和限制有何不同?的主要内容,如果未能解决你的问题,请参考以下文章

DFA和NFA的区别

正规式与正规集,DFA与NFA

NFA转化为DFA的子集构造算法和DFA最简化

nfa 与 dfa 的时间复杂度权衡

编译原理NFA转DFA ,请问DFA的初始状态如何确定?

C语言实现NFA转DFA