假設文本串爲S,模式串爲P
此篇博客只是爲了輔助理解KMP算法,更好的理解KMP算法模板,詳細推導過程並未說明。
1.暴力匹配
for (int i = 0; i < n; i++)
{
bool flag = true;
for (int j = 0; j < m; j++)
{
if (s[i+j] != p[j])
{
flag=false;
break;
}
}
if(flag) {
//匹配成功
}
}
2.KMP
KMP算法就是通過某些預處理(next數組),使得每次如果Si和Pj不匹配時,不再直接回溯 j 的位置到起始位置,而是回溯到某一箇中間位置(next[j+1]),繼續匹配,且不再回溯 i 的位置。如下圖:
()
從上圖應該可以很容易的看出KMP和樸素做法的區別,且如果知道不匹配時j
應跳轉的位置(next數組),可以很容易寫出代碼。實際上整個KMP算法代碼很是很簡短的
//匹配過程
for(int i = 0, j = -1; i < m; i++) { //m表示文本串長度
while(j != -1 && s[i] != p[j+1]) { //不匹配則回溯j到ne[j]位置
j = ne[j];
}
if(s[i] == p[j+1]) { //當前位置匹配,判斷下一個位置
j++;
}
if(j == n-1) { //匹配到結尾,則再文本串位置找到模式串
res++; //res表示模式串在匹配串中個數,在求其他問題時,可變換
j = ne[j]; //繼續尋找下一個模式串
}
}
那麼,整個KMP的重點就是理解next數組了。
上圖中L1, L2,L3, L4,L5表示的字符串序列是綠色框中字符串(字符串序列用線段表示了)。那麼顯然L1 == L2,L3 == L4。同時可以得出L5 == L4。當第 j 個元素不匹配時,變換後的前綴(P0…ne[j])和變換前的後綴相等(從第 j-1 個元素向前取相同長度字符)。
next[j]表示的是P0…j字符串的前綴與後綴相等的最長長度-1(-1是因爲下標從0開始,實質上是最大長度)。
且在求解next[j]時,只和模式串P有關,而和文本串S無關。
理解了next[]數組的含義後,那麼也可以發現求next[]數組本身也是一種字符串匹配得過程。
int ne[MAXN]; //next數組,ne[j]表示P~0...j~字符串的前綴與後綴相等的最長長度-1(-1是因爲下標從0開始,實質上是最大長度)
void init(int n, string p) { //n表示模板串長度,p表示模板串
ne[0] = -1; //-1表示長度爲0
for(int i = 1, j = -1; i < n; i++) { //i指向文本串,j指向模式串
while(j != -1 && p[i] != p[j+1]) {
j = ne[j];
}
if(p[i] == p[j+1]) {
j++;
}
ne[i] = j;
}
}