1. 前文回顾

  在字符串算法—字符串搜索中,我们实现了从一堆字符中搜索某个字符串的高效算法。

  但如果要在一堆字符中找具有某些规律的字符串(要找的字符串是不确定的,但有规律),该如何设计算法?

  本文将介绍NFA算法来解决此问题。

 

2. 正则表达式

  首先,有规律的字符串是长什么样的呢?例如:ABBBBBBBBBA。

  我们需要用一种方式来表达这个规律。于是我们就有了正则表达式。

  ABBBBBBBBBA可以表达为AB*A。其中B*表示有N个B,N可以为0。

  然后以下均是正则表达式:

  

  

  [A-E]+表示:(A|B|C|D|E)(A|B|C|D|E)*

  

  有了这些表达式后,我们就能让程序读懂字符串的规律,然后就是让它找出含有这规律的字符串在哪了。

  

3. NFA算法

  本算法需要有向图基础,如果不了解有向图的,可以先看一下图表算法—有向图

  从例子中开始了解此算法:

  现有正则表达式: ( ( A * B | A C ) D )。

  我们通过某种构建方法(此方法稍后介绍),把这个正则表达式变成一个有向图:

  

 

  我们将逐个逐个符号来读这个表达式。图中的0,1,2,3,...,10,11是阶段,当我们抵达第11阶段,则说明找到符合这表达式的字符串。

  图中的线有两种颜色,红色的是过渡线,可以不读阶段里面的内容,直接跳到下一个指定阶段;蓝色是正常线,要先读阶段里面的内容,再改变阶段。

  例如:第4阶段只能去第5阶段;第1阶段可以去第2阶段、第3阶段、第4阶段、第6阶段。

  现在我们来看一下AAAABD是否符合这个表达式:

  第一个字符为A,我们从第0阶段开始:

  

  0,1阶段都是括弧,故符合。现在1可以去2、3、4和6,且2和6都是A,与第一个字符匹配,故两个阶段都去:

  

  第二个字符为A,现在可以去2,4,7(其中去2的路线为2-3-2,去4的路线为2-3-4),但匹配第二个字符的只有第2阶段,故去第2阶段:

  

  第三个字符为A,现在可以去2,4(其中去2的路线为2-3-2,去4的路线为2-3-4),但匹配第三个字符的只有第2阶段,故去第2阶段:

  

  第四个字符为A,现在可以去2,4(其中去2的路线为2-3-2,去4的路线为2-3-4),但匹配第四个字符的只有第2阶段,故去第2阶段:

  

  第五个字符为B,现在可以去2,4(其中去2的路线为2-3-2,去4的路线为2-3-4),但匹配第五个字符的只有第4阶段,故去第4阶段:

  

  第六个字符为D,现在可以去9(其中去9的路线为4-5-8-9,由于5,8都是符号,不能与字符比较,故无视它们),第9阶段匹配第五个字符,故去第9阶段:

 

  没有第七个字符,且第9阶段可以直接抵达第11阶段,故这个字符串符合此表达式。

  如果要从这个字符串中,找出有多少个符合此表达式的字符串,则经过上面的寻找,我们找到了第一个:AAAABD。

  接下来寻找第二个,从第二个字符开始第0阶段,如果能顺利抵达第11阶段,说明AAABD也符合;否则,AAABD不符合。

  然后第三个,从第三个字符开始第0阶段,如此类推,直到所有字符都开始过第0阶段为止,然后统计有多少个字符串符合,且记住它们的位置。

  如此类推,我们可以找出整段字符中符合这个表达式的所有字符串,从而解决一开始提出的问题。

  接下来就要看如何把这个正则表达式转化为有向图了:

  首先,如果某阶段里含有的不是符号,而是字符,则可以直接去下一个阶段:

  

  然后,如果遇到的是符号 * ,则分两种情况:

  1. *所处的阶段的前一阶段是字符:

  

  则加3条方向(不用担心重复加方向,因为代码中如果出现重复的,直接覆盖)。

  2. *所处的阶段的前一阶段是右括弧:

  

  也是加3条方向:与前一个左括弧加一个互相的方向,我们会用一个数字lp来记住前一个左括弧。

  回到我们的这个例子,给*阶段添加方向:(符号用的线都是过渡线

  

  如果遇到的是左括弧或者是 | 则要把它们按顺序加到栈中,顺便给左括弧加一个指向下一个阶段的方向:(新建栈A)

  

 

  如果遇到的是右括弧,则给它加一个指向下一个阶段的方向,且栈A输出数个元素,直到见到左括弧为止:

  

 

 

  然后栈A输出一个元素(先进先出):第5阶段的 | ,新建一个整数int or=5来记住它,并且给它加一个指向目前所处位置的右括弧处:

  

 

  然后栈A输出一个元素:第1阶段的(,给它加一个指向or+1位置的方向:(如果有多个or,则分别指向各个or+1位置)

  

 

 

  然后遇见的是第10阶段的),给它加一个指向下一个阶段的方向,且栈A输出数个元素,直到见到左括弧为止:

  

 

  然后栈A输出一个元素:第0阶段的(,中途没见到 |, 因此不作操作:

  

 

  然后去到第11阶段,构建完毕。

代码实现:

  根据正则表达式构建有向图:

  

  图中红色箭头处,它默认了栈下一个吐出来的是左括号,但如果吐出来的是 | ,则会出错,这里应该做一个针对连续吐出多个 | 的处理(加个判断)。

  这段代码只处理了符号,应该添加代码给字符所处的阶段指向下一个阶段。

  使用这个有向图来寻找字符串: