我已经给Mark Byers +1了-但是据我所知,除了解释为什么一种算法不好而另一种更好的算法外,关于正则表达式匹配的工作原理并没有说太多。也许链接中有东西?
我将专注于好的方法-创建有限的自动机。如果您将自己限制在确定性的自动机上而没有最小化,那么这并不是很难。
我将(很快)描述的是Modern Compiler Design中采用的方法。
假设您有以下正则表达式...
a (b c)* d
字母表示要匹配的文字字符。*是通常的零个或多个重复匹配。基本思想是基于虚线规则导出状态。我们将零状态设为尚未匹配的状态,因此点位于最前面...
0 : .a (b c)* d
唯一可能的匹配是'a',因此我们得出的下一个状态是...
1 : a.(b c)* d
现在,我们有两种可能性-匹配“ b”(如果至少有一个“ b c”重复)或匹配“ d”。注意-我们基本上是在这里进行有向图搜索(深度优先或广度优先等等),但是在搜索时会发现有向图。假设采用广度优先的策略,我们需要将其中一种情况排入队列,以供以后考虑,但从现在开始,我将忽略该问题。无论如何,我们发现了两个新状态...
2 : a (b.c)* d
3 : a (b c)* d.
状态3是结束状态(可能不止一个)。对于状态2,我们只能匹配“ c”,但是之后需要注意点的位置。我们得到“ a。(bc)* d”-与状态1相同,因此我们不需要新的状态。
IIRC,现代编译器设计中的方法是在您碰到运算符时转换规则,以简化点的处理。状态1将转换为...
1 : a.b c (b c)* d
a.d
也就是说,您的下一个选择是匹配第一个重复或跳过重复。此状态中的下一个状态等效于状态2和3。此方法的优点是,您可以丢弃所有过去的比赛(“。”之前的所有内容),因为您只关心将来的比赛。这通常会提供较小的状态模型(但不一定是最小的状态模型)。
编辑如果您确实放弃了已经匹配的详细信息,那么状态描述就是从那时起可能出现的一组字符串的表示。
在抽象代数方面,这是一种集合闭包。代数基本上是具有一个(或多个)运算符的集合。我们的集合是状态描述,而我们的运算符是我们的过渡(字符匹配)。封闭集是将任何运算符应用于集合中的任何成员时始终产生集合中另一个成员的集合。集合的闭合是闭合的最小的较大集合。因此,基本上,从明显的起始状态开始,我们正在构造相对于过渡运算符集闭合的最小状态集-可达状态的最小集。
最小是指关闭过程-可能会有一个较小的等效自动机,通常称为最小自动机。
牢记这一基本思想,说“如果我有两个代表两组字符串的状态机,如何导出代表联合的第三个状态机”(或交集,或集差...),就不是太难了。您的状态表示法将代替每个输入自动机的当前状态(或一组当前状态),还可能附加其他详细信息,而不是虚线规则。
如果您的常规语法变得复杂,则可以将其最小化。这里的基本思想比较简单。您将所有状态分组为一个等效类或“块”。然后,您反复测试是否需要针对特定的过渡类型拆分块(状态实际上不是等效的)。如果特定块中的所有状态都可以接受相同字符的匹配,并且这样做时到达相同的下一个块,则它们是等效的。
Hopcrofts算法是处理此基本思想的有效方法。
关于最小化的一个特别有趣的事情是,每个确定性有限自动机都恰好具有一个最小形式。此外,Hopcrofts算法将以最小形式产生相同的表示形式,而不管它从哪个大写开始。也就是说,这是一个“规范”表示形式,可用于派生哈希值或用于任意但一致的排序。这意味着您可以使用最少的自动机作为容器的键。
上面的内容可能有点草率的WRT定义,因此请确保您在自己使用任何术语之前先查找它们,但是幸运的是,这可以快速地介绍基本概念。
顺便说一句-看看Dick Grunes网站的其余部分-他有一本关于解析技术的免费PDF书籍。《现代编译器设计》的第一版相当不错的IMO,但是正如您将看到的那样,即将有第二版。