引言
我們把尋找字符串A(模式串)在字符串B(主串)第一次完整地出現的位置,把這個過程叫做字符串匹配,例如下圖:
在這種模式匹配中,最粗暴簡單的方法:
- 開始之前記個k=0作爲匹配失敗的次數,i作爲模式串比較字符的位置,j作爲主串比較字符的位置;
- 匹配成功時,接續向下一個字符進行匹配,直到匹配模式串匹配完成;
- 當發現匹配失敗時,模式串重新到首個字符,而主串則回溯到k+1的位置,k自加1;
![image](https://imgconvert.csdnimg.cn/aHR0cHM6Ly9pbWcuYWxpY2RuLmNvbS9pbWdleHRyYS9pMi8yNTk2NTMzOTcyL08xQ04wMU5ZN1ZWeDFmRERqWGkya1R1XyEhMjU5NjUzMzk3Mi5wbmc?x-oss-process=image/format,png)
這種方法是最簡單的,但同時也是最低效的:因爲在匹配的過程中,需要同時回溯i,j兩個參數;但是這種回溯大部分都是多餘的;爲了解決這種低效,1977由Donald Knuth、Vaughan Pratt、James H. Morris三人於年聯合了一個字符串模式匹配算法,Knuth-Morris-Pratt 字符串查找算法,簡稱爲 “KMP算法”。
代碼實現:
int index(Str str,Str substr)
{
int i=1,j=1,k=i;
while(i <= str.length && j<= substr.length)
{
if (str.ch[i]==substr[j])
{
++i;
++j;
}
else
{
j = 1;
i =++k; #匹配失敗;
}
}
if(j > substr.length)
{
return k;
}
else
{
return 0;
}
}
KMP算法的匹配流程
假設主串S匹配到j的位置,模式串P匹配到i的位置,還沒匹配結束,繼續匹配下去會出現下面兩種情況:
- 1,當P[i] == S[j]時,則繼續匹配 i++,j++;
- 2,當P[i] != S[j]時,則 i =next[i],j++,此時等價於模式串P向右移動了 i -next[i]個單位;
以上只是帶大家初步認識了KMP算法的基本匹配思想,下面則需要進行深入地瞭解一下next數組,以及對匹配思想的理解;
第一步,字符串相同的前後綴元素
在理解next數組之前,需要直到next數組是怎麼求出來的,首先我們看一下下面的這張圖
圖中我們瞭解到幾個信息:
- S爲主串;P爲模式串;
- 圖中的S[i-k] — S[i]部分與P中的P[j-k]到P[j](模式串P的前面)部分相同,同時也與P[0]到P[k](模式串P的前面)相同;
- 模式串向後移動了j-next[j]個單位;
從以上幾部分信息,可以這樣理解:
- 在P未移動之前之前有部分連續字符串與S已經匹配成功了,同時P的前面一小部分也能匹配成功,所以直接可以把P的首個字符移動到與後面字符串重複地方的開始字符,也就是P[j-k];
- 因爲只是部分連續,所以在移動的過程捕獲出現字符串匹配成功的可能。
上面重複的部分就是模式串的相同前後綴的最大長度,也就等於next函數,而移動的距離剛好是 j(匹配失敗的位置) - next[j](前面字符串前後綴重複的最大長度)
字符串重複前後綴的最大長度也就是字符串前面的連續字符(從第一個開始)有多少個與後面的相同,自身與自身不算同一個。如下圖:
也就能求出next數組,只不過是關於後一字符的;
第二步,已知next[1]....next[j],怎樣求得next[j+1]
在第一步中,已經分析過了,next數組的值與前後綴的最大長度相關,而關於最大長度得出的next值 是後一字符的next數組的值;
這裏,有一種比較權威的求next[j+1]的方法(這裏是以第一個字符未p[0]爲依據的):
- next[0] = -1,因爲第一個字符比較特殊;
- 若p[k] == p[j],則next[j+1] = next[j]+1 =k+1;
- 若p[k ] ≠ p[j],如果此時p[next[k]] == p[j ],則next[j+1] =next[k] + 1,否則繼續遞歸前綴索引k = next[k],而後重複此過程。
注意到沒,k字符在這裏起到的是最大長度值的作用,例如下面這張圖,
- 當j = 2時,k =0, p[k]= A 則因爲p[3]!=p[k],所以 k =next[k]一直遞歸到 k =-1,則next[3] =k+1 =0;
- 依次類推我們可以瞭解到當 p[j] =D 時,next[j] =k +1 =2 ;
- 當p[j+1] = E時,next[j+1] = next[next[k]] +1 = 0;
總結
整個分析到這裏基本上也就結束了,整個KMP算法的關鍵核心是求next函數,next函數需要我們聯繫到模式串的前後綴最大長度值。KMP算法雖然在字符串的匹配過程中著以高效,但依然存在着自己的一些弊端,一些大佬在此基礎之上又進一步地對算法進行了優化。
KMP算法代碼實現:
void getnext(Str substr,int next[])
{
int i = 1,j = 0;
next[1] = 0;
while(i < substr.length)
{
if(j==0||substr.ch[i] == str.ch[j])
{
i++;
j++;
next[i] = j;
}
else
{
j =next[j]
}
}
}
int KMP(Str str, Str substr, int next[])
{
int i=1,j=1;
while(i<= str.length && j <= substr.length)
{
if(j == 0 || str.ch[i] == substr.ch[j])
{
++i;
++j;
}
else
{
j =next[j];
}
}
if(j > substr.length)
{
return i -substr.length;
}
else
{
return 0;
}
}
關於優化的過程主要是next函數向nextval函數的轉變,關於優化方法,我會在以後單寫一篇文章用來講述,敬請關注!