KMP算法
kmp算法是一种改进的字符串匹配算法,由D.E.Knuth与V.R.Pratt和J.H.Morris同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是根据给定的模式串W1,m,定义一个next函数。next函数包含了模式串本身局部匹配的信息。
基本思想
假设在模式匹配的进程中,执行T[i]和W[j]的匹配检查。若T[i]=W[j],则继续检查T[i+1]和W[j+1]是否匹配。若T[i]<>W[j],则分成两种情况:若j=1,则模式串右移一位,检查T[i+1]和W[1]是否匹配;若1<j<=m,则模式串右移j-next(j)位,检查T[i]和W[next(j)]是否匹配。重复此过程直到j=m或i=n结束。
文献中,朱洪对KMP算法作了修改,他修改了KMP算法中的next函数,即求next函数时不但要求W[1,next(j)-1]=W[j-(next(j)-1),j-1],而且要求W[next(j)]<>W[j],他记修改后的next函数为newnext。显然在模式串字符重复高的情况下,朱洪的KMP算法比KMP算法更加有效。
以下给出朱洪的改进KMP算法和next函数和newnext函数的计算算法。
KMP串匹配算法
输入: 正文串T[1,n]和模式串W[1,m]
输出: 匹配结果match[1,n]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
intKMP(stringW,stringT){ inti=1,j=1; while (i<=n){ while (j!=0&&W[j]!=T[i]){ j=next[j]; } if (j==m){ returni-m+1; //success,returnthefirstmatchposition } else { j++;i++; } } return -1; //failure } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
procedureKMP begin i= 1 j= 1 whilei<=ndo whilej<>0andW[j]<>T[i] do j=newnext[j] endwhile ifj=m return“success” else j++ i++ endif endwhile return“failure” end |
next和newnext函数算法
输入: 模式串W[1,m]
输出: next[1,m+1]和newnext[1,m]
1
2
3
4
5
6
7
8
9
10
11
12
|
voidgetNext(stringW){ for (inti=1;i<m;i++){ intj=i; while (j>0){ j=next[j]; if (W[j]==W[i]){ next[i+1]=j+1; break ; } } } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
functionNEXT begin next[ 1 ]=newnext[ 1 ]= 0 j= 2 whilej<=mdo i=next[j- 1 ] whilei<>0andW[i]<>W[j- 1 ]) do i=next[i] endwhile next[j]=i+ 1 j=j+ 1 endwhile end functionNEWNEXT begin newnext⑴= 0 j= 2 whilej<=mdo i=next(j) ifi=0orW[j]<>W[i+ 1 ] newnext[j]=i else newnext[j]=newnext[i] endif j++ endwhile end |
朱洪证明了算法1的时间复杂度为O(n),算法2的时间复杂度为O(m)。
更加简洁的算法
下面是更加简洁的算法:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
voidGetNext(charT[],intnext[]) { next[1]=0; j=1;k=0; while (j<T[0]) if ((k==0)||(T[j]==T[k])) { j++; k++; next[j]=k; } elsek=next[k]; } |
计算过程
假设在执行正文中自位置i起“返前”的一段与模式的自右至左的匹配检查中,一旦发现不匹配(不管在什么位置),则去执行由Wm与ti+d(x)起始的自右至左的匹配检查,这里x是字符t。它的效果相当于把模式向右滑过d(ti)一段距离。显然,若ti不在模式中出现或仅仅在模式末端出现,则模式向右滑过的最大的一段距离m。图1.1示出了执行BM算法时的各种情况。实线连接发现不匹配以后要进行比较的正文和模式中的字母,虚线连接BM算法在模式向右滑后正文和模式中应对齐的字母,星号表示正文中的一个字母。
图1.1:执行BM算法时的各种情况
BM算法由算法1.3给出,函数d的算法由算法1.4给出。计算函数d的时耗显然是Θ(m)。BM算法的最坏情况时耗是Θ(mn)。但由于在实用中这种情况极少出现,因此BM算法仍广泛使用。
BM串匹配算法
输入: 正文串W[1,m]和模式串T[1,n]
输出: 匹配结果match[1,n]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
procedureBM begin i=m Repeat j=m k=i while (j> 0 ) and (w[j]=t[k]) do j=j- 1 k=k- 1 endwhile i=i+d[t[i]] Until (j= 0 ) or (i>n) Ifj=0return“SUCCESS” elsereturn“FAILURE” endif end |
d函数计算法
1
2
3
4
5
6
7
8
9
|
functiond:integer; begin forx∈∑dod(x)=m forj=m-1downto1do ifd(w[j])=md(w[j]):=m-j endfor end xi+1=ord(ti+1)dm-1+ord(ti+2)dm-2+…+ord(ti+m) =(xi-ord(ti)dm-1).d+ord(ti+m) |
因此有 h(xi+1)=((h(xi)-x·ord(ti))·d+ord(ti+m)mod q ,i=1,2,……,n-m
这里x是一常数,x=dm-1mod q。这就是计算每一长度为m的字符段的散列函数值的递推公式。RK串匹配算法由算法1.5给出。
RK串匹配算法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
programRK; begin {计算x,x:=d↑(m-1)modq} x=1 fori=1tom-1dox=(32*x)modq {计算模式W的散列函数值} s=0 fori=1tomdo s=((s*32)+ord(w[i]))modq {计算正文T的第一个长度为m的字符段的散列函数值} t=0 fori=1tomdo t=(t*32+ord(w[i]))modq {如果正文的第一个长度为m的字符段和模式有相同的散列函数值,则进行匹配检查.否则,以及在匹配检查失败情况下,继续计算下一个字符段的散列函数值} i=1 whilei<=n-mdo ifs=t {进行匹配检查} k=1 j=i while(t[j]=w[k])and(k<=m)do j=j+1 k=k+1 endwhile ifi<n-m{计算下一字符段的散列函数值} t=((t-x*ord(t[i]))*32+ord(t[i+m]))modq i=i+1 endif endif endwhile return“FAILURE” end |
显然,如果不计执行匹配检查的时间,则RK算法的剩余部分执行时间是Θ(m+n)。不过,如果计及执行匹配检查的时间,则在理论上,RK算法需要时耗Θ(mn)。但是,我们总可设法取q适当大,使得mod函数在计算机中仍可执行而冲突(即不同的字符串具有相同的散列值)又极小可能发生,而使算法的实际执行时间只需Θ(m+n)。
BM算法
BM算法和KMP算法的差别是对模式串的扫描方式自左至右变成自右至左。另一个差别是考虑正文中可能出现的字符在模式中的位置。这样做的好处是当正文中出现模式中没有的字符时就可以将模式大幅度滑过正文。
BM算法的关键是根据给定的模式W[1,m],,定义一个函数d: x->{1,2,…,m},这里x∈∑。函数d给出了正文中可能出现的字符在模式中的位置。
2优化编辑
优化思路
KMP算法是可以被进一步优化的。
我们以一个例子来说明。譬如我们给的P字符串是“abcdaabcab”,经过KMP算法,应当得到“特征向量”如下表所示:
下标i
|
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
p(i)
|
a
|
b
|
c
|
d
|
a
|
a
|
b
|
c
|
a
|
b
|
next[i]
|
-1
|
0
|
0
|
0
|
0
|
1
|
1
|
2
|
3
|
1
|
但是,如果此时发现p(i) == p(k),那么应当将相应的next[i]的值更改为next[k]的值。经过优化后可以得到下面的表格:
下标i
|
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
p(i)
|
a
|
b
|
c
|
d
|
a
|
a
|
b
|
c
|
a
|
b
|
next[i]
|
-1
|
0
|
0
|
0
|
0
|
1
|
1
|
2
|
3
|
1
|
优化的next[i]
|
-1
|
0
|
0
|
0
|
-1
|
1
|
0
|
0
|
3
|
0
|
(1)next[0]= -1 意义:任何串的第一个字符的模式值规定为-1。
(2)next[j]= -1 意义:模式串T中下标为j的字符,如果与首字符
相同,且j的前面的1—k个字符与开头的1—k
个字符不等(或者相等但T[k]==T[j])(1≤k<j)。
如:T=”abCabCad” 则 next[6]=-1,因T[3]=T[6]
(3)next[j]=k 意义:模式串T中下标为j的字符,如果j的前面k个
字符与开头的k个字符相等,且T[j] != T[k] (1≤k<j)。
即T[0]T[1]T[2]。。。T[k-1]==
T[j-k]T[j-k+1]T[j-k+2]…T[j-1]
且T[j] != T[k].(1≤k<j);
(4) next[j]=0 意义:除(1)(2)(3)的其他情况。
补充一个next[]生成代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
voidgetNext(constchar*pattern,intnext[]) { next[0]=-1; intk=-1,j=0; while (pattern[j]!= '\0' ) { while (k!=-1&&pattern[k]!=pattern[j])k=next[k]; ++j; ++k; if (pattern[k]==pattern[j]) next[j]=next[k]; elsenext[j]=k; } } |
算法代码C++
KMP算法查找串S中含串P的个数count
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
#include<iostream> #include<stdlib.h> #include<vector> usingnamespacestd; inlinevoidNEXT(conststring&T,vector< int >&next) { //按模式串生成vector,next(T.size()) next[0]=-1; for (inti=1;i<T.size();i++){ intj=next[i-1]; while (T[i]!=T[j+1]&&j>=0) j=next[j]; //递推计算 if (T[i]==T[j+1])next[i]=j+1; elsenext[i]=0; // } } inlinestring::size_typeCOUNT_KMP(conststring&S, conststring&T) { //利用模式串T的next函数求T在主串S中的个数count的KMP算法 //其中T非空, vector< int >next(T.size()); NEXT(T,next); string::size_typeindex,count=0; for (index=0;index<S.size();++index){ intpos=0; string::size_typeiter=index; while (pos<T.size()&&iter<S.size()){ if (S[iter]==T[pos]){ ++iter;++pos; } else { if (pos==0)++iter; elsepos=next[pos-1]+1; } } //whileend if (pos==T.size()&&(iter-index)==T.size())++count; } //forend returncount; } intmain(intargc, char *argv[]) { stringS= "abaabcacabaabcacabaabcacabaabcacabaabcac" ; stringT= "ab" ; string::size_typecount=COUNT_KMP(S,T); cout<<count<<endl; system ( "PAUSE" ); return0; } |
算法源码Pascal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
PROGRAMImpl_KMP; USESCRT; CONSTMAX_STRLEN= 255 ; VARnext: array [ 1.. MAX_STRLEN]ofinteger; str_s,str_t: string ; int_i: integer ; Procedureget_nexst(t: string ); Varj,k: integer ; Begin j:= 1 ;k:= 0 ; whilej<Length(t)dobegin if (k= 0 ) or (t[j]=t[k])thenbegin j:=j+ 1 ;k:=k+ 1 ; next[j]:=k; end elsek:=next[k]; end ; End ; Functionindex(s: string ;t: string ): integer ; Vari,j: integer ; Begin get_next(t); index:= 0 ; i:= 1 ;j:= 1 ; while (i<=Length(s)) and (j<=Length(t))dobegin if (j= 0 ) or (s[i]=t[j])thenbegin i:=i+ 1 ;j:=j+ 1 ; end elsej:=next[j]; ifj>Length(t)thenindex:=i-Length(t); end ; End ; BEGIN ClrScr; {清屏,可不要} Write (‘s=’); Readln(str_s); Write (‘t=’); Readln(str_t); int_i:=index(str_s,str_t); ifint_i<>0thenbegin Writeln ( 'Found' ,str_t, 'in' ,str_s, 'at' ,int_i, '.' ); end elseWriteln( 'Cannotfind' ,str_t, 'in' ,str_s, '.' ); END . |
index函数用于模式匹配,t是模式串,s是原串。返回模式串的位置,找不到则返回0。