這次真的理解了。。。
看下面。。。。。。
1.
首先,字符串"BBC ABCDAB ABCDABCDABDE"的第一個字符與搜索詞"ABCDABD"的第一個字符,進行比較。因爲B與A不匹配,所以搜索詞後移一位。
2.
因爲B與A不匹配,搜索詞再往後移。
3.
就這樣,直到字符串有一個字符,與搜索詞的第一個字符相同爲止。
4.
接着比較字符串和搜索詞的下一個字符,還是相同。
5.
直到字符串有一個字符,與搜索詞對應的字符不相同爲止。
6.
這時,最自然的反應是,將搜索詞整個後移一位,再從頭逐個比較。這樣做雖然可行,但是效率很差,因爲你要把"搜索位置"移到已經比較過的位置,重比一遍。
7.
一個基本事實是,當空格與D不匹配時,你其實知道前面六個字符是"ABCDAB"。KMP算法的想法是,設法利用這個已知信息,不要把"搜索位置"移回已經比較過的位置,繼續把它向後移,這樣就提高了效率。
8.
怎麼做到這一點呢?可以針對搜索詞,算出一張《部分匹配表》(Partial Match Table)。這張表是如何產生的,後面再介紹,這裏只要會用就可以了。
9.
已知空格與D不匹配時,前面六個字符"ABCDAB"是匹配的。查表可知,最後一個匹配字符B對應的"部分匹配值"爲2,因此按照下面的公式算出向後移動的位數:
移動位數 = 已匹配的字符數 - 對應的部分匹配值
因爲 6 - 2 等於4,所以將搜索詞向後移動4位。
10.
因爲空格與C不匹配,搜索詞還要繼續往後移。這時,已匹配的字符數爲2("AB"),對應的"部分匹配值"爲0。所以,移動位數 = 2 - 0,結果爲 2,於是將搜索詞向後移2位。
11.
因爲空格與A不匹配,繼續後移一位。
12.
逐位比較,直到發現C與D不匹配。於是,移動位數 = 6 - 2,繼續將搜索詞向後移動4位。
13.
逐位比較,直到搜索詞的最後一位,發現完全匹配,於是搜索完成。如果還要繼續搜索(即找出全部匹配),移動位數 = 7 - 0,再將搜索詞向後移動7位,這裏就不再重複了。
14.
下面介紹《部分匹配表》是如何產生的。
首先,要了解兩個概念:"前綴"和"後綴"。 "前綴"指除了最後一個字符以外,一個字符串的全部頭部組合;"後綴"指除了第一個字符以外,一個字符串的全部尾部組合。
15.
next數組的含義
重點來了。下面解釋一下next數組的含義,這個也是KMP算法中比較不好理解的一點。
令原始串爲: S[i],其中0<=i<=n;模式串爲: T[j],其中0<=j<=m。
假設目前匹配到如下位置
S0,S1,S2,...,Si-j,Si-j+1...............,Si-1, Si, Si+1,....,Sn
T0,T1,.....................,Tj-1, Tj, ..........
S和T的綠色部分匹配成功,恰好到Si和Tj的時候失配,如果要保持i不變,同時達到讓模式串T相對於原始串S右移的話,可以更新j的值,讓Si和新的Tj進行匹配,假設新的j用next[j]表示,即讓Si和 next[j]匹配,顯然新的j值要小於之前的j值,模式串纔會是右移的效果,也就是說應該有next[j] <= j -1。那新的j值也就是next[j]應該是多少呢?我們觀察如下的匹配:
1)如果模式串右移1位(從簡單的思考起,移動一位會怎麼樣),即next[j] = j - 1, 即讓藍色的Si和Tj-1匹配(注:省略號爲未匹配部分)
S0,S1,S2,...,Si-j,Si-j+1...............,Si-1, Si, Si+1,....,Sn
T0,T1,.....................,Tj-1, Tj, .......... (T的劃線部分和S劃線部分相等【1】)
T0,T1,.................Tj-2,Tj-1, ....... (移動後的T的劃線部分和S的劃線部分相等【2】)
根據【1】【2】可以知道當next[j] =j -1,即模式串右移一位的時候,有T[0 ~ j-2] == T[1 ~ j-1],而這兩部分恰好是字符串T[0 ~j-1]的前綴和後綴,也就是說next[j]的值取決於模式串T中j前面部分的前綴和後綴相等部 分的長度(好好揣摩這兩個關鍵字概念:前綴、後綴,或者再想想,我的上一篇文章,從Trie樹談到後綴樹中,後綴樹的概念)。
2)如果模式串右移2位,即next[j] = j - 2, 即讓藍色的Si和Tj-2匹配
S0,S1,...,Si-j,Si-j+1,Si-j+2...............,Si-1, Si, Si+1,....,Sn
T0,T1,T2,.....................,Tj-1, Tj, ..........(T的劃線部分和S劃線部分相等【3】)
T0,T1,...............,Tj-3,Tj-2,.........(移動後的T的劃線部分和S的劃線部分相等【4】)
同樣根據【3】【4】可以知道當next[j] =j -2,即模式串右移兩位的時候,有T[0 ~ j-3] == T[2 ~ j-1]。而這兩部分也恰好是字符串T[0 ~j-1]的前綴和後綴,也就是說next[j]的值取決於模式串T中j前面部分的前綴和後綴相等部分的長度。
3)依次類推,可以得到如下結論:當發生失配的情況下,j的新值next[j]取決於模式串中T[0 ~ j-1]中前綴和後綴相等部分的長度, 並且next[j]恰好等於這個最大長度。
4)按照遞推的思想:
根據定義next[0]=-1,假設next[j]=k, 即P[0...k-1]==P[j-k,j-1]
1)若P[j]==P[k],則有P[0..k]==P[j-k,j],很顯然,next[j+1]=next[j]+1=k+1;
2)若P[j]!=P[k],則可以把其看做模式匹配的問題,即匹配失敗的時候,k值如何移動,顯然k=next[k]。
16.
"部分匹配"的實質是,有時候,字符串頭部和尾部會有重複。比如,"ABCDAB"之中有兩個"AB",那麼它的"部分匹配值"就是2("AB"的長度)。搜索詞移動的時候,第一個"AB"向後移動4位(字符串長度-部分匹配值),就可以來到第二個"AB"的位置。
17.模式串右移1位,即next[j] = j - 1(記住這一點)