代码改变世界

KMP算法的学习笔记

2011-04-12 01:29  Aga.J  阅读(416)  评论(2编辑  收藏  举报

串的模式匹配

问题:从主串中找到子串第一次出现的位置

解决:

1) Brute-Force算法

最简单的算法就是将子串的第一个字符与主串的第一个字符比较,如果匹配,则比较两者的下一个字符,如果失败,则将子串的第一个字符和主串的第一个比较字符的下一个字符进行比较,重复上述操作,直到子串匹配完成或者结束。

最好情况下,只需要m次比较(m为子串长度)即可完成。

当子串的第0个字符和主串的每一个字符都不同的,比较次数为n-m+1,而当每次匹配时,子串前面的所有字符都和主串的相应字符相同,但是最后一个字符不同,这样一来就需要比较(n-m+1)*m次,因为每次匹配失败可以看做是【当子串的第0个字符和主串的每一个字符都不同的,比较次数为n-m+1】这种情况,而匹配失败的过程是【子串前面的所有字符都和主串的相应字符相同,但是最后一个字符不同】,所以最坏的情况下,需要比较的次数是(n-m+1)*m

2) KMP算法

假设以下两串的比较过程:

clip_image002

我们可以观察到b和c的比较其实是没必要的,因为主串中的前3个字符不等,而第一次比较的结果告诉我们,子串和主串的前3个字符相等,所以再使用子串的首字符去和主串的第2,3字符比较式没有必要的。

再看第四次比较,因为子串中第1,2,3个字符和第4,5,6个字符相同,而在第一次匹配过程中,主串和子串的第4,5,6个字符也相同,所以说子串的第1,2,3个字符和主串的第4,5,6个字符是匹配的,所以这三个字符的比较野是没有必要的,所以我们一开始就可以将主串的第6个字符(刚好是第一次匹配时的失配点)和子串的第3个字符比较。

一般性特征:主串s和模式t在匹配过程中,发生了失配,即t[j]和s[i]不等,设k为模式失配点的前一个字符t[j-1]为结束字符的子串的前缀的字符数,即 t[0]t[1]..t[k-1]=t[j-k]t[j-k+1]..t[j-1],所以主串的失配点s[i]下一步只要和t[k]继续比较下去就可以了。所以关键在于在模式的任何位置上,找到他的最大前缀。

(首先,失配后,前面几个abc不需要再比较,这我们很容易理解,而为什么可以从第6个字符开始去和子串的第4个字符开始比较呢?这是因为我们找到了k,k使得公式t[0]t[1]..t[k-1]=t[j-k]t[j-k+1]..t[j-1]成立,也就是说不仅模式串中t[0]t[1]..t[k-1]=t[j-k]t[j-k+1]..t[j-1],而且主串中t[0]t[1]..t[k-1]=s[i-k]s[i-k+1]..s[i-1],所以我们不在需要从s[i-k]和t[0]开始比较,我们要做的就是比较s[i]和t[k],也就是将主串的失效点和模式串的k点开始比较!而关键就是在计算这个k)

下面这个函数可以刻画我们需要的k的求值

clip_image004

求j位置(从0开始)的失效函数值,也就是求k,使得k满足

clip_image006得到的k位置就是失效函数值(k不等于j)。

注意到这里k是小于j而不是小于等于j,如果等于j了,那么下一个比较点是s[j]和t[k]还是比较失效点。如果以t[j]结束的子串找不到相应的前缀,那么就返回-1。

clip_image008

接下来是如何根据一个模式串,求出其失效的函数的值

推导过程:(失效函数的实现)

假设模式t=t[0]t[1]..t[m-1],j为模式的指针,j=0,从左向右求f(j),f(0)=-1。

假设f(j)=x,求f(j+1)?

因为f(j)=x,所以 t[0]t[1]..t[x]=t[j-x]t[j-x+1]..t[j]

如果 t[x+1]=t[j+1],那么f(j+1)=x+1

如果t[x+1]!=t[j+1],这时候不能轻易的下结论说f(j+1)= -1,为什么?可以举个例子说明,也可以这样解释,就算t[x+1]!=t[j+1],也有可能找到一个数m使得t[0]t[1]..t[m]=t[j+1-m]…t[j+1].

所以这个时候,我们就把x+1作为模式串与主串的匹配失效点,所以我们又可以利用失效函数,来找最长前缀,根据前面提到的那样,我们这次求f(x).得到的值y就是x之前的最长前缀,使得  t[0]..t[y]=t[j-y]..t[j],所以下一步就是判断t[y+1]是否等于t[j+1].如果等于的话,那么f(j+1)=y+1,如果不等于的话,那么就再继续求f(y).重复这个过程直到匹配结束。

所以,

clip_image010

算法设计:

1 f(0)= -1

2 假设已知f(1),f(2),f(3),,,f(j)

3 求f(j+1)

如果 t[j+1] = t[i+1] 那么f(j+1)=f(j)+1 (4-9)

如果 t[j+1] != t[i+1] 那么

只要找到最小的x使得满足 clip_image012

那么f(j+1)就去u+1 或者 -1

算法实现:

Int String::Find(const String &t,int start) //String类提供的Find

{

Int I = start; //起始位置

Int j;

Int *f = new int[length]; //创建失效值数组

F[0]= -1; //F[0]初始化为-1,已知F[0]

For( j=1;j<length;j++) //开始求失效数组,初始化数组

{

Int u = f [j-1]; //前一个的失效值,即 t[0]t[1]..t[u] = t[j-u]..t[j]

While ( t.str[j] != t.str[u+1] && u>=0 ) u=f[u]; //这里就判断t[j]是否等于t[u+1],当然如果u= -1的话就不用比较了。

//如果不相等,那么就对u再求f,这和我们上面说到的过程一致。

If ( t.str[j] == t.str[u+1] ) f[j]=u+1; //式4-7,(注意式4-7得到的结果又会绕回去4-9的情况)

Else f[j] = -1; //式4-8

//最后一个的判断

}

J=0; //根据失效数组进行判断

While( i<length && j<length) //匹配

{

If ( t.str[j] == str[i] ) { ++i; ++j;} //如果匹配,则前移

Else if( j==0) i++; //否则的话,如果j为0,则i++,如果j不为零,那就取得它的失效值再加一,得到新的比较位置j

Else j= f [ j-1 ]+1;

}

Delete []f;

If ( j<length ) return -1;

Else return i-j; //匹配成功

}