我目前正在研究正则表达式的问题,当与某个输入匹配时,它可能最终会在指数时间内运行,例如(a*)*
,当与字符串 aaaaab 匹配时(a|a)*
可能会出现“灾难性回溯” - 对于每个额外的“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?)*