【问题标题】:Regex (a?)* not exponential?正则表达式(a?)*不是指数的?
【发布时间】:2012-08-21 12:29:13
【问题描述】:

我目前正在研究正则表达式的问题,当与某个输入匹配时,该问题最终会在指数时间内运行,例如,(a*)*(a|a)* 在匹配字符串时可能会出现“catastrophic backtracking” aaaaab - 对于匹配字符串中的每个额外的“a”,尝试匹配字符串所需的时间加倍。仅当引擎使用回溯/NFA 方法尝试在失败之前尝试树中所有可能的分支时才会出现这种情况,例如 PCRE 中使用的方法。

我的问题是,为什么(a?)* 没有漏洞?根据我对回溯的理解,字符串“aaaab”中应该发生的事情本质上就是(a|a)* 发生的事情。如果我们使用标准的 Thomspson NFA 构造来构造 NFA,那么对于发生的每个 epsilon 转换,引擎肯定必须继续采用它们并以与两个 a 的情况相同的方式回溯?例如(省略一些步骤以及 @ 替换 epsilon 的地方):

"aaaa" 匹配,但不能匹配 'b',失败(回溯)
"aaaa@" 匹配,'b' 失败(回溯)
"aaa@a" 匹配,'b' 失败(回溯)
"aaa@a@" 匹配,'b' 失败(回溯)
...
"@a@a@a@a@" 匹配,'b' 失败(回溯)

尝试所有可能的 epsilons 和 a 组合,肯定会导致路由的指数级爆炸?

从 NFA 中删除 epsilon 转换是有意义的,但我相信这会从 (a*)* 模式中删除所有非确定性。不过这绝对是脆弱的,所以我不完全确定发生了什么!

非常感谢您!

编辑: Qtax 已经指出,当使用传统回溯遍历 NFA 时,epsilons 不能仍然存在,否则(@)* 将尝试永远匹配。那么,什么样的 NFA 实现可能会导致 (a*)*(a|a)* 呈指数增长,而 (a?)* 不是这样呢?这确实是问题的症结所在。

【问题讨论】:

  • PCRE 和 Perl 正则表达式引擎在许多方面进行了优化。您的所有示例都将很快失败,而不会发生灾难性的回溯,即使是在大字符串上也是如此。
  • 我知道这一点,也许一个更好的例子是 Java 的正则表达式引擎,它不会很快失败。 PCRE 和 Perl 的早期版本也遇到了同样的问题,我的问题可以应用到他们身上!但是,在那些似乎容易受到 (a*)* 等模式影响的引擎(即未优化的引擎)上,(a?)* 似乎没问题 - 为什么?!
  • 优化。量化的零宽度表达式(没有副作用)是没用的,如果按照您的描述使用它(尝试匹配a,然后每次重复都为空字符串),那么正则表达式永远不会失败,因为您可以无限地重复()*
  • 关于()* 的一个好点。我发现很难理解现有实现如何优化以阻止这种情况发生。一个非常幼稚的实现,通过 Thompson 的构造(e-moves 和所有)简单地构造 NFA,然后尝试回溯它永远不会因为你提到的 ()* 原因而终止,那么他们做了什么导致 (a?)* 到成功,但(a*)* 失败?它不能像完全删除电子动作那么简单,因为我认为这会导致(a*)* 具有确定性
  • 如果您想要一个正则表达式引擎如何处理这些情况的示例,您可以查看Perl's regex debug 输出,它向您展示了表达式编译的内容和匹配的每一步:^(a*)*$ 和@987654324 @。示例命令(win):perl -Mre=debug -e "'aaab' =~ /^(a?)*$/"

标签: regex theory pcre backtracking finite-automata


【解决方案1】:

好的,经过一番调查,我最终发现这是由于在 NFA 实现中使用了“障碍”。简单地说,障碍被放置在 NFA 中的战略点上(例如在 a* 的 NFA 构造中的“a”转换之后的节点上)。他们要求比赛从上一次击中障碍开始。这可以防止 NFA 陷入与无限数量的 epsilon 匹配的情况并允许它终止。

换句话说,不可能从一个障碍到仅匹配 e-moves 的同一障碍 - 如果发生这种情况,路线将被丢弃并从前一点开始回溯。这也有一个副作用,即 (a?)* 不易受到指数爆炸的影响,因为 a?第二次无法匹配 null。

【讨论】:

    猜你喜欢
    • 2014-04-23
    • 1970-01-01
    • 1970-01-01
    • 2011-10-04
    • 1970-01-01
    • 1970-01-01
    • 2013-02-01
    • 2015-11-22
    • 2011-09-17
    相关资源
    最近更新 更多