8.4 NFA和DFA

上一节粗略介绍了回溯,它是NFA特有的功能,DFA不需要回溯,也就不需要保存状态再反复尝试。这样看来,NFA不是更慢吗?但是当前我们所使用的大多数工具中的正则引擎都选用了NFA,这是为什么?

NFA确实更慢,但NFA也有自己的优势:如果正则表达式比较复杂,构建NFA的时间比DFA的时间更短(举例来说,如果你的正则表达式使用了多选分支,那么完全可以直接对每个多选分支构建简单的NFA,再把它们并列起来就可以了;相比之下,构建整个表达式的DFA就复杂多了)。

同时,现代NFA也提供了更多的优化措施,比如之前提到的a(bb)+a的匹配,优化过的NFA可以“并行尝试”,其匹配过程如图所示,这样的速度就快多了。

[图片上传失败...(image-91d320-1545311241772)]

更重要的是,NFA的匹配性质决定了它必须在匹配过程中保存可能的状态,需要“停下来四处看看”,也能够“回顾一路走来的历程”;相比之下,DFA不会两次测试同一个字符,所以不需要保存状态,因此,NFA具有许多DFA无法提供的功能:比如捕获型括号、反向引用、环视功能、忽略优先量词...

如果需要用到这些功能,一定不要选择使用DFA引擎的工具。当谈一般来说,用户并不需要关心引擎是DFA或NFA,毕竟它们是位于“幕后”的,需要关注的只是是否提供了希望实现功能用到的API。

而且,现代的一些工具中,为兼顾效率和功能,同时包含了DFA和NFA两种引擎,如果发现正则表达式中没有专属于NFA的功能,则使用DFA,否则使用NFA。表8-1中列出了各种常用工具使用的正则引擎。

表8-1 常用工具所使用的正则引擎

引擎类型 程序
DFA awk(大多数版本)、egrep(大多数版本)、flex、lex、MySql、Procmail
NFA GNU Emacs、java、grep、less、more、.net、python、ruby、php、sed

细分起来,NFA又分为传统型NFA(Traditional NFA)和POSIX NFA两种。两者点主要区别在于,如果多选分枝中点多个分支都能匹配,传统型NFA优先选择左侧点分枝,而POSIX NFA一定要选择最长的分支。比如用表达式(jeff|jeffrey)匹配字符串jeffrey,POSIX NFA的结果是jeffrey,传统型NFA的结果是jeff——如果调换多选分支的顺序,写成(jeffrey|jeff),POSIX NFA的结果不变,而传统型NFA的结果变为jeffrey。

问题看起来很复杂,具体使用却比较简单:POSIX NFA的应用很少,主要用于Linux/Unix下的工具(所以它们中的很多并不支持捕获分组),本书中介绍的编程语言基本都采用传统型NFA引擎,遇到多选分支时优先采用最左侧的分支。

推荐阅读更多精彩内容

  • 构建扫描器程序的两个重要方法 boolean hitEnd() 返回true,说明结尾有更多的字符可能会改变本次的...
    mxl0814阅读 94评论 0 1
  • 匹配基础 对于正则表达式,有两条普适原则: 优先选择最左端的匹配结果; 标准的匹配量词(*、+、?和{min, m...
    戴小白阅读 2,473评论 0 6
  • 初衷:看了很多视频、文章,最后却通通忘记了,别人的知识依旧是别人的,自己却什么都没获得。此系列文章旨在加深自己的印...
    DCbryant阅读 2,191评论 0 19
  • 背景 最近公司规范出来后,关于字符串不提倡用 “ + ” 进行拼接,于是自己写了个function,利用正则...
    Cassie_CHM阅读 2,816评论 0 1
  • 全民创业、万众创新的时代下,创业,尤其是互联网创业备受追捧。互联网的发展也经历了PC互联网、移动互联网、再到今天的...
    从0开始学产品阅读 107评论 0 2