這個算法的目標是實現字符串查找功能,解決了字符串查找中匹配失敗後回溯重新匹配的問題。
目標:在 abbaabbaaba字符串中找到 abbaaba 。
甲(data):abbaabbaaba
乙(pattern):abbaaba
傳統的暴力求解:甲從頭開始與乙一一比較,發現第 7 個字符不匹配。甲會回退到自己的第 2 個字符,乙則回退到自己的開頭,然後兩人開始重新比較。然後 不匹配,回退,不匹配,回退,……
KMP的核心思想:甲不回退,只進行乙回退。那麼,乙需要回退多少才能保證甲即使不回退,甲之前位置的字符與乙就匹配呢?這時候就需要引入 最大公共元素單元長度 這個概念了。下面的表格簡述了最大公共元素單元長度的計算方式。
pattern最大公共元素單元:
長字符串的最大公共元素是在短子字符串的最大公共元素基礎上增加的,如 A, AB, ABC, ABCA ....
這個結論很重要,後面乙回退的位置都是子字符串的最大公共元素所處於的偏移位置。
所有最大公共元素單元長度組成的數據就是我們需要的next數組,那怎麼求呢?
next數組中第n個位置存儲的是長度爲n的字符串中的最大公共元素單元長度。
爲什麼k縮小公式爲 k=next[k-1] ?
其實本質上就是找k公共子串以外的其他公共子串,最長的不行,挑短的試試。
之所以是next[k-1],是因爲 next[k-1]的最長公共子串,同樣也是pattern[k]的公共子串,只是不是最長的而已。(根據對稱性很容易得到)而pattern[k-1] pattern[k-2]這些並不是pattern[k]的公共子串。
構造next數組
void make_next(char *pattern, int *next)
{
int q, k;
int m = strlen(pattern);
next[0] = 0;
for(q = 1, k = 0; q < m; q++)
{
while (k > 0 && pattern[q] != pattern[k])
k = next[k-1];
if (pattern[q] == pattern[k])
{
k++;
}
next[q] = k;
}
}
KMP算法
KMP的過程非常接近next的求取過程,不過會將next提前算好。data數據每次移後一個位置,然後遍歷next去進行匹配,不過這兒的next遍歷的步長不是1,也不是固定的 ,而是當前pattern[j]字符串最大公共子串位置,也就是 j = next[j-1]。
爲什麼這麼選,上面也有說了,next[j-1]位置的公共子串也是pattern[j]的公共子串,只不過長度短了些。而pattern[j-1] pattern[j-2]這些並不是pattern[j]的公共子串。
int kmp(char *data, char *pattern)
{
int i, j;
int n = strlen(data);
int m = strlen(pattern);
if (n < m)
{
return -1;
}
int next[100] = {0};
make_next(pattern, next);
for (i = 0, j = 0; i < n; i++)
{
// 此步驟後,如果沒有匹配到,j的值爲0,所以下次i循環時,j會從0開始
// 每一次i循環,都會按照next子串匹配一遍,成功 j==m,跳出循環;
// 否則繼續下次循環,再遍歷next子串。
// 之所以是next[j-1],是因爲 next[j-1]的最長公共子串,
// 同樣也是pattern[j]字符串的公共子串,只是不是最長的而已。(根據對稱性很容易得到)
while (j > 0 && data[i] != pattern[j])
j = next[j - 1];
if (data[i] == pattern[j])
{
j++;
}
if (j == m)
break;
}
if (j == m)
return i-m+1;
else
return -1;
}