KMP主要用於字符串匹配,比如給一個長度爲n的字符串s,然後再給一個長度爲m的字符串t,問s中有沒有連續子串和t一樣(n>=m)
顯然,直接暴力求解,容易得到最壞O(m*(n-m))複雜度的算法。當這個樸素算法的複雜度不滿足要求的時候,就需要用到我們經典的字符串匹配算法——KMP,複雜度爲O(m+n)
如下圖所示,i指針指向字符串s,j指針指向t,當i從第一個字符a開始與t匹配,當i指到紅框裏面的那個c,j指向d,發現c和d不匹配(稱爲失配)。按照暴力的方法,此次失配後,i將回到s的第二個字符b,而j回到t的起點,依次匹配,但實際上,因爲我們已經知道了i前面是abccab,也就是說i往前移只會重複匹配,很多情況下並不會匹配成功——由圖中可知,i最多要向前移動兩位,纔會使得s[i]==t[0],但這時j要指向t的開始——實際上i向前移動兩位也是一種重複匹配,還是因爲我們已經知道i前面的字符是abccab——顯然,i和j之前都是完全匹配的,爲了不重複匹配,i是不能前移的,那麼只能讓j前移了,假設j前移到了k位,正好和i匹配了,也就是說abccab的前k-1位和後k-1位完全吻合,並且abccab的前k位即t[k]要等於s[i],所以j就移到了那個c處
可以說理解了上面那個圖,就理解了KMP
額,要是不能理解,我也扯不下去了(我的博客大概是全網說KMP的說的最短的了吧~)這玩意兒好難講啊,還是直接上代碼吧~~~
// 在字符串T裏找P,輸出所有匹配點
#include <iostream>
#include <cstring>
using namespace std;
const int sz = 1010;
int f[sz];
void getFail(char* p, int* f) // 由p預處理處f[],f[x]表示上文中的j指針跑到x時發現不匹配,應該跳到f[x]處繼續匹配
{
int m = strlen(p);
f[0] = 0, f[1] = 0;
for(int i = 1; i < m; ++ i)
{
int j = f[i];
while(j && p[i] != p[j]) j = f[j];
f[i+1] = p[i] == p[j] ? j+1 : 0;
}
}
void find(char* T, char* P, int* f)//KMP匹配過程
{
int n = strlen(T), m = strlen(P);
getFail(P, f);
int j = 0;
for(int i = 0; i < n; ++ i)
{
while(j && P[j] != T[i]) j = f[j];
if(P[j] == T[i]) ++ j;
if(j == m)
printf("%d\n", i-m+1);
}
}
int main()
{
char P[sz] = "", T[sz] = "";
scanf("%s%s", T, P);
find(T, P, f);
return 0;
}