KMP字符串模式匹配詳解
KMP字符串模式匹配通俗點說就是一種在一個字符串中定位另一個串的高效算法。簡單匹配算法的時間複雜度爲O(m*n);KMP匹配算法。可以證明它的時間複雜度爲O(m+n).。
int Index_BF ( char S [ ], char T [ ], int pos )
{
int i = pos, j = 0;
while ( S[i+j] != '/0'&& T[j] != '/0')
if ( S[i+j] == T[j] )
else
{
i ++; j = 0; // 重新開始新的一輪匹配
if ( T[j] == '/0')
else
如:T=”abCabCad” 則 next[6]=-1,因T[3]=T[6]
下標
|
0
|
1
|
2
|
3
|
4
|
T
|
a
|
b
|
c
|
a
|
c
|
next
|
-1
|
0
|
0
|
-1
|
1
|
下標
|
0
|
1
|
2
|
3
|
4
|
T
|
a
|
b
|
c
|
a
|
b
|
next
|
-1
|
0
|
0
|
-1
|
0
|
下標
|
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
T
|
a
|
b
|
a
|
b
|
c
|
a
|
a
|
b
|
c
|
next
|
-1
|
0
|
-1
|
0
|
2
|
-1
|
1
|
0
|
2
|
下標
|
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
T
|
a
|
b
|
C
|
a
|
b
|
C
|
a
|
d
|
next
|
-1
|
0
|
0
|
-1
|
0
|
0
|
-1
|
4
|
next[5]= 0 根據 (3) 雖T[0]T[1]=T[3]T[4],但T[2]==T[5]
下標
|
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
T
|
a
|
d
|
C
|
a
|
d
|
C
|
a
|
d
|
next
|
-1
|
0
|
0
|
-1
|
0
|
0
|
-1
|
0
|
void get_nextval(const char *T, int next[])
{
int j = 0, k = -1;
next[0] = -1;
while ( T[j/*+1*/] != '/0' )
{
if (k == -1 || T[j] == T[k])
{
++j; ++k;
if (T[j]!=T[k])
next[j] = k;
else
next[j] = next[k];
else
k = next[k];
void getNext(const char* pattern,int next[])
{
next[0]= -1;
int k=-1,j=0;
while(pattern[j] != '/0')
{
if(k!= -1 && pattern[k]!= pattern[j] )
k=next[k];
++j;++k;
if(pattern[k]== pattern[j])
next[j]=next[k];
else
next[j]=k;
}
}
#include <iostream.h>
{
if( !Text||!Pattern|| Pattern[0]=='/0' || Text[0]=='/0' )//
int len=0;
const char * c=Pattern;
{
}
int index=0,i=0,j=0;
while(Text[i]!='/0' && Pattern[j]!='/0' )
{
if(Text[i]== Pattern[j])
{
++j;
}
else
{
index += j-next[j];
if(next[j]!=-1)
else
{
j=0;
++i;
}
}
if(Pattern[j]=='/0')
else
return -1;
int main()//abCabCad
{
char* text="bababCabCadcaabcaababcbaaaabaaacababcaabc";
char*pattern="adCadCad";
//getNext(pattern,n);
//get_nextval(pattern,n);
cout<<KMP(text,pattern)<<endl;
return 0;
五.其他表示模式值的方法
上面那種串的模式值表示方法是最優秀的表示方法,從串的模式值我們可以得到很多信息,以下稱爲第一種表示方法。第二種表示方法,雖然也定義next[0]= -1,但後面絕不會出現 -1,除了next[0],其他模式值next[j]=k(0≤k<j)的意義可以簡單看成是:下標爲j的字符的前面最多k個字符與開始的k個字符相同,這裏並不要求T[j] != T[k]。其實next[0]也可以定義爲0(後面給出的求串的模式值的函數和串的模式匹配的函數,是next[0]=0的),這樣,next[j]=k(0≤k<j)的意義都可以簡單看成是:下標爲j的字符的前面最多k個字符與開始的k個字符相同。第三種表示方法是第一種表示方法的變形,即按第一種方法得到的模式值,每個值分別加1,就得到第三種表示方法。第三種表示方法,我是從論壇上看到的,沒看到詳細解釋,我估計是爲那些這樣的編程語言準備的:數組的下標從1開始而不是0。
下面給出幾種方法的例子:
表一。
下標 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
T |
a |
b |
a |
b |
c |
a |
a |
b |
c |
(1) next |
-1 |
0 |
-1 |
0 |
2 |
-1 |
1 |
0 |
2 |
(2) next |
-1 |
0 |
0 |
1 |
2 |
0 |
1 |
1 |
2 |
(3) next |
0 |
1 |
0 |
1 |
3 |
0 |
2 |
1 |
3 |
第三種表示方法,在我看來,意義不是那麼明瞭,不再討論。
表二。
下標 |
0 |
1 |
2 |
3 |
4 |
T |
a |
b |
c |
a |
c |
(1)next |
-1 |
0 |
0 |
-1 |
1 |
(2)next |
-1 |
0 |
0 |
0 |
1 |
表三。
下標 |
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
T |
a |
d |
C |
a |
d |
C |
a |
d |
(1)next |
-1 |
0 |
0 |
-1 |
0 |
0 |
-1 |
0 |
(2)next |
-1 |
0 |
0 |
0 |
1 |
2 |
3 |
4 |
對比串的模式值第一種表示方法和第二種表示方法,看錶一:
第一種表示方法next[2]= -1,表示T[2]=T[0],且T[2-1] !=T[0]
第二種表示方法next[2]= 0,表示T[2-1] !=T[0],但並不管T[0] 和T[2]相不相等。
第一種表示方法next[3]= 0,表示雖然T[2]=T[0],但T[1] ==T[3]
第二種表示方法next[3]= 1,表示T[2] =T[0],他並不管T[1] 和T[3]相不相等。
第一種表示方法next[5]= -1,表示T[5]=T[0],且T[4] !=T[0],T[3]T[4] !=T[0]T[1],T[2]T[3]T[4] !=T[0]T[1]T[2]
第二種表示方法next[5]= 0,表示T[4] !=T[0],T[3]T[4] !=T[0]T[1] ,T[2]T[3]T[4] !=T[0]T[1]T[2],但並不管T[0] 和T[5]相不相等。換句話說:就算T[5]==’x’,或 T[5]==’y’,T[5]==’9’,也有next[5]= 0 。
從這裏我們可以看到:串的模式值第一種表示方法能表示更多的信息,第二種表示方法更單純,不容易搞錯。當然,用第一種表示方法寫出的模式匹配函數效率更高。比如說,在串S=“adCadCBdadCadCad 9876543”中匹配串T=“adCadCad”, 用第一種表示方法寫出的模式匹配函數,當比較到S[6] != T[6] 時,取next[6]= -1(表三),它可以表示這樣許多信息: S[3]S[4]S[5]==T[3]T[4]T[5]==T[0]T[1]T[2],而S[6] != T[6],T[6]==T[3]==T[0],所以S[6] != T[0],接下來比較S[7]和T[0]吧。如果用第二種表示方法寫出的模式匹配函數,當比較到S[6] != T[6] 時,取next[6]= 3(表三),它只能表示:S[3]S[4]S[5]== T[3]T[4]T[5]==T[0]T[1]T[2],但不能確定T[6]與T[3]相不相等,所以,接下來比較S[6]和T[3];又不相等,取next[3]= 0,它表示S[3]S[4]S[5]== T[0]T[1]T[2],但不會確定T[3]與T[0]相不相等,即S[6]和T[0] 相不相等,所以接下來比較S[6]和T[0],確定它們不相等,然後纔會比較S[7]和T[0]。是不是比用第一種表示方法寫出的模式匹配函數多繞了幾個彎。
爲什麼,在講明第一種表示方法後,還要講沒有第一種表示方法好的第二種表示方法?原因是:最開始,我看嚴蔚敏的一個講座,她給出的模式值表示方法是我這裏的第二種表示方法,如圖:
她說:“next 函數值的含義是:當出現S[i] !=T[j]時,下一次的比較應該在S[i]和T[next[j]] 之間進行。”雖簡潔,但不明瞭,反覆幾遍也沒明白爲什麼。而她給出的算法求出的模式值是我這裏說的第一種表示方法next值,就是前面的get_nextval()函數。匹配算法也是有瑕疵的。於是我在這裏發帖說她錯了:
http://community.csdn.net/Expert/topic/4413/4413398.xml?temp=.2027246
現在看來,她沒有錯,不過有張冠李戴之嫌。我不知道,是否有人第一次學到這裏,不參考其他資料和明白人講解的情況下,就能搞懂這個算法(我的意思是不僅是算法的大致思想,而是爲什麼定義和例子中next[j]=k(0≤k<j),而算法中next[j]=k(-1≤k<j))。憑良心說:光看這個講座,我就對這個教受十分敬佩,不僅講課講得好,聲音悅耳,而且這門課講得層次分明,恰到好處。在KMP這個問題上出了點小差錯,可能是編書的時候,在這本書上抄下了例子,在那本書上抄下了算法,結果不怎麼對得上號。因爲我沒找到原書,而據有的網友說,書上已不是這樣,也許吧。說起來,教授們研究的問題比這個高深不知多少倍,哪有時間推演這個小算法呢。總之,瑕不掩玉。
書歸正傳,下面給出我寫的求第二種表示方法表示的模式值的函數,爲了從S的任何位置開始匹配T,“當出現S[i] !=T[j]時,下一次的比較應該在S[i]和T[next[j]] 之間進行。” 定義next[0]=0 。
void myget_nextval(const char *T, int next[])
{
// 求模式串T的next函數值(第二種表示方法)並存入數組 next。
int j = 1, k = 0;
next[0] = 0;
while ( T[j] != '/0' )
{
if(T[j] == T[k])
{
next[j] = k;
++j; ++k;
}
else if(T[j] != T[0])
{
next[j] = k;
++j;
k=0;
}
else
{
next[j] = k;
++j;
k=1;
}
}//while
for(int i=0;i<j;i++)
{
cout<<next[i];
}
cout<<endl;
}// myget_nextval
下面是模式值使用第二種表示方法的匹配函數(next[0]=0)
int my_KMP(char *S, char *T, int pos)
{
int i = pos, j = 0;//pos(S 的下標0≤pos<StrLength(S))
while ( S[i] != '/0' && T[j] != '/0' )
{
if (S[i] == T[j] )
{
++i;
++j; // 繼續比較後繼字符
}
else // a b a b c a a b c
// 0 0 0 1 2 0 1 1 2
{ //-1 0 -1 0 2 -1 1 0 2
i++;
j = next[j]; /*當出現S[i] !=T[j]時,
下一次的比較應該在S[i]和T[next[j]] 之間進行。要求next[0]=0。
在這兩個簡單示範函數間使用全局數組next[]傳值。*/
}
}//while
if ( T[j] == '/0' )
return (i-j); // 匹配成功
else
return -1;
} // my_KMP
六.後話--KMP的歷史
Cook於1970年證明的一個理論得到,任何一個可以使用被稱爲下推自動機的計算機抽象模型來解決的問題,也可以使用一個實際的計算機(更精確的說,使用一個隨機存取機)在與問題規模對應的時間內解決。特別地,這個理論暗示存在着一個算法可以在大約m+n的時間內解決模式匹配問題,這裏m和n分別是存儲文本和模式串數組的最大索引。Knuth 和Pratt努力地重建了 Cook的證明,由此創建了這個模式匹配算法。大概是同一時間,Morris在考慮設計一個文本編輯器的實際問題的過程中創建了差不多是同樣的算法。這裏可以看到並不是所有的算法都是“靈光一現”中被發現的,而理論化的計算機科學確實在一些時候會應用到實際的應用中。