正则表达式中的反向引用如何进行回溯?

时间:2012-06-19 12:55:13

标签: regex computer-science complexity-theory time-complexity backreference

我阅读了http://swtch.com/~rsc/regexp/regexp1.html,其中作者说,为了在正则表达式中进行反向引用,在匹配时需要回溯,这使得最坏情况的复杂度呈指数级增长。但我并不确切地知道为什么反向引用会引入回溯的必要性。有人可以解释为什么,也许提供一个例子(正则表达式和输入)?

4 个答案:

答案 0 :(得分:20)

要直接回答您的问题,您应该对Chomsky Hierarchy进行简短的研究。这是一种古老而美丽的方式,以越来越复杂的方式组织形式语言。层次结构的最低行是常规语言。你可能会猜测 - 而且你是对的 - RL正是那些可以用"纯粹"正则表达式:只有字母表,空字符串,连接,交替|和Kleene星*的那些(看起来很麻,没有后面引用)。形式语言理论的经典定理--Kleene定理 - 是DFA,NFA(如您引用的文章中所述)和正则表达式都具有完全相同的幂来表示和识别语言。文章中给出的汤普森结构是定理证明的一部分。

每个RL也是CFL。但是有无数的CFL并不规律。 CFL中存在的一个特性使得它们太复杂而不规则是平衡的事物对:括号,开始结束块等。几乎所有的编程语言都是CFL。 CFL可以通过所谓的下推自动机进行有效识别。这实际上是一个粘贴了堆栈的NFA。堆栈增长到所需的大小,因此它不再是有限自动机。实际编程语言的解析器几乎都是下推自动机的变种。

考虑使用反向引用的正则表达式

^(b*a)\1$

用文字表示,对于某些n,这表示长度为2n的字符串,其中第n个和第2个字符都是a,所有其他字符都是b。这是一个不常规的CFL的完美例子。你可以用另一个很酷的形式语言工具 - 泵浦引理来严格证明这一点。

这正是后引用导致问题的原因!他们允许"正则表达式"代表不规律的语言。因此,没有NFA或DFA可以识别它们。

但等等,它比我到目前为止做得还要糟糕。考虑

^(b*a)\1\1$

我们现在有一个长度为3n的字符串,其中第n个,第2个,第3个和第3个元素为a,其他所有元素都为b。泵浦引理还有另一种风格,可以证明这种语言甚至太复杂而不能成为CFL!没有下推自动机可以识别这个。

后向引用允许这些超级正则表达式表示乔姆斯基层次结构中三个级别的语言:上下文敏感语言。粗略地说,识别CSL的唯一方法是检查相同长度语言的所有字符串(至少如果P!= NP,但这对于所有实际目的和完全不同的故事都是如此)。此类字符串的数量在您匹配的字符串长度中呈指数级。

这就是需要搜索正则表达式匹配器的原因。您可以非常聪明地设计搜索方式。但总会有一些输入驱使它采取指数时间。

所以我同意你引用的论文的作者。使用 no back refs 编写完全无辜的正则表达式是可能的,几乎所有输入都能有效识别,但是存在一些导致Perl或Java或Python正则表达式匹配的输入 - 因为这是一个回溯搜索 - 需要数百万年才能完成比赛。这太疯狂了。你可以拥有一个正确且可正常工作多年的脚本,然后锁定一天只是因为它偶然发现了其中一个不良输入。假设正则表达式被隐藏在您正在驾驶的飞机中的导航系统的消息解析器中......

修改

根据要求,我将简要介绍抽样引理如何用来证明语言a^k b a^k b不规则。此处a^ka重复k次的简写。 PL表示必须存在正整数N,使得长度至少为N的常规语言中的每个字符串必须具有R S T的形式,使得R S ^ k T也是所有自然k的语言。此处RST为字符串,S可能不为空。

PL的证明取决于每种常规语言对应于某些DFA的事实。此DFA的可接受输入长于其状态数(等于引理中的L)必须使其成为"循环:"重复一个州。调用此状态X.机器消耗一些字符串R从开始到X,然后S循环回X,然后T到达接受状态。好吧,在输入中添加S(或者删除S)的额外副本仅对应于不同数量的"循环"从X回到X.因此,还将接受带有附加(或删除)S副本的新字符串。

由于每个 RL必须满足PL,因此证明语言不规则的证据表明它与PL相矛盾。对于我们的语言,这并不难。假设您试图说服我L = a^k b a^k b的语言满足PL。因为它会这样做,你必须能够给我一些N的值(见上文):假设DFA中识别L的状态数。此时,我会说,"好的Mr. Regular Guy ,考虑字符串B = a^N b a^N b。"如果L是常规的,B必须使这个DFA(无论它看起来如何)在前N个字符内循环,这些字符必须全是a个!所以循环(上面的字符串S)也包括所有a s。有了这个,我可以立即证明你对L是正常的说法是错误的。我只是选择第二次绕圈。这将导致您的假设DFA接受新字符串a^M b a^N b,其中M> N,因为我将a添加到其上半部分。哎哟!这个新字符串不在L中,所以PL毕竟不是真的。因为无论你提供什么N,我每次都可以做这个技巧,PL不能保持L,毕竟L不能是常规的。

由于它不规律,Kleene定理告诉我们没有DFA也没有FA,也没有"纯粹"描述它的正则表达式。

证明反向引用允许甚至没有上下文的语言具有非常相似的环,但需要在下推自动机上有背景,我不打算在这里给出。谷歌将提供。

注意:这两个都不能证明后面的参考使得识别NP完整。他们只是以非常严格的方式说,后面的引用为纯正则表达式增加了真正的复杂性。它们允许使用任何具有有限内存的机器识别的语言,也不允许任何只有无限大LIFO内存的语言。我会将NP完整性证明留给其他人。

答案 1 :(得分:10)

NFA和DFA是Finite Automata,也就是有限状态机,它们是“抽象机器,可以处于有限数量的状态之一” [1]。注意有限状态数。

链接文章Regular Expression Matching Can Be Simple And Fast中讨论的快速NFA / DFA算法很快,因为它们可以使用有限个状态(与输入长度无关),如制品

引入反向引用会使状态数(几乎)为“无限”(在最坏的情况下约为256 n 其中 n 的长度为输入)。状态数量增加是因为每个反向引用的每个可能值都成为自动机的状态。

因此,使用有限状态机不再适合/可能,并且必须使用回溯算法。

答案 2 :(得分:4)

本教程中有一些很好的例子:
http://www.regular-expressions.info/brackets.html

您将感兴趣的特定情况显示在'Backtracking Into Capturing Groups'中 - 它解释了如何在最后一个匹配可以被发现之前如何放弃整个匹配匹配整个正则表达式。此外,值得注意的是,这可能会导致意外的匹配。

答案 3 :(得分:1)

非常有趣的文档:Extending Finite Automata to Efficiently Match Perl-Compatible Regular Expressions,支持反向引用并使用修改后的NFA有效计算出现次数。