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

blu*_*ers 38 regex finite-automata dfa nfa

我正在寻找基于其功能和限制的DFA与NFA引擎之间差异的非技术性解释.

Dav*_*ley 70

确定性有限自动机(DFAs)和非确定性有限自动机(NFA)具有完全相同的能力和局限性.唯一的区别是符号方便.

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

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

现在,我们可以将"读取数字或标识符"表示为一个州本身,突然间我们不需要NFA.如果我们将NFA中的状态组合表示为状态本身,我们就会得到一个DFA,其状态比NFA多得多,但它们也会做同样的事情.

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


jam*_*iss 14

以下是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算法示例:http://swtch.com/~rsc/regexp/regexp1.html (15认同)

Tim*_*ker 6

一个简单的,非技术性的解释,从Jeffrey Friedl的着作" 掌握正则表达式"一书中解释.

CAVEAT:

虽然这本书通常被认为是"正则表达",但是在DFA和NFA之间的区别是否真的是正确的,似乎存在一些争议.我不是计算机科学家,我不理解真正是"常规"表达背后的大多数理论,无论是否具有确定性.在争议开始之后,我因此而删除了这个答案,但从那以后它在评论中引用了其他答案.我会非常有兴趣进一步讨论这个问题 - 弗里德尔真的错了吗?或者我得到弗里德错了(但我昨天晚上重读那一章,就像我记得的那样......)?

编辑:看来弗里德尔和我确实是错的.请查看以下Eamon的优秀评论.


原始答案:

一个DFA引擎一步步输入字符串的字符和尝试字符(并记住)所有可能的方式,正则表达式可以匹配在这一点上.如果它到达字符串的末尾,则表示成功.

想象一下字符串AAB和正则表达式A*AB.我们现在逐字逐句地逐字逐句.

  1. A:

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

    • 第一个分支:可以通过扩展进行匹配A*.
    • 第二分支:无法匹配B.第二个分支失败.但:
    • 第三个分支:可以通过不扩展A*和使用第二个分支进行匹配A.
  3. B:

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

DFA引擎永远不会在字符串中回溯.


一个NFA引擎通过步骤正则表达式的记号标记,并试图在字符串中的所有可能的排列,回溯如果必要的.如果它到达正则表达式的末尾,则声明成功.

想象一下与以前相同的字符串和相同的正则表达式.我们现在通过令牌逐步执行我们的正则表达式令牌:

  1. A*:匹配AA.记住回溯位置0(字符串的开头)和1.
  2. A:不匹配.但我们有一个回溯的位置,我们可以回来再试一次.正则表达式引擎退回一个字符.现在A匹配.
  3. B: 火柴.达到了正则表达式的结束(有一个回溯位置).万岁!

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