特別感謝 orz sofu6 讓我悟了ios
\(KMP\) 算法指的是字符串模式匹配算法,要解決的問題就是在字符串(也叫主串)中的模式(pattern)定位問題算法
說簡單點就是咱們平時常說的關鍵字搜索。模式串就是關鍵字(接下來稱它爲P),若是它在一個主串(接下來稱爲T)中出現,就返回它的具體位置,不然返回-1(經常使用手段)。數組
從左到右一個個匹配,若是這個過程當中有某個字符不匹配,就跳回去,將模式串向右移動一位函數
咱們能夠這樣初始化:優化
以後咱們只須要比較 \(i\) 指針指向的字符和 \(j\) 指針指向的字符是否一致。若是一致就都向後移動,若是不一致spa
\(A\)和\(E\)不相等,那就把 \(i\) 指針移回第 \(1\) 位(假設下標從 \(0\) 開始),j移動到模式串的第\(0\)位,而後又從新開始這個步驟:3d
顯然確定會T掉,怎麼優化??指針
KMP算法code
利用已經部分匹配這個有效信息,保持 \(i\) 指針不回溯,經過修改 \(j\) 指針,讓模式串儘可能地移動到有效的位置blog
那麼當發現一個字符與主串不匹配時,\(j\) 指針應該移向哪兒??
探究 \(j\) 移動的規律
C 和 D 不匹配了把 \(j\) 移動到哪兒??顯然是第一位
下面狀況相同
能夠把 \(j\) 移到第2位,由於前面有兩個字母已經匹配
得出KMP精髓
\(j\) 要移動的下一個位置 \(k\) 要保證最前面 \(k\) 個字符 \(j\) 以前的最後 \(k\) 個字符是同樣的
\(P[0 => k-1] == P[j-k => j-1]\)
另外一種理解
若子串的前綴集和後綴集中,重複的最長子串的長度爲\(k\),則下次匹配子串的j能夠移動到第 \(k\) 位(下標爲0爲第0位)
在「aba」中,前綴集就是除掉最後一個字符'a'後的子串集合{a,ab},同理後綴集爲除掉最前一個字符a後的子串集合{a,ba},那麼二者最長的重複子串就是a,k=1;
在「ababa」中,前綴集是{a,ab,aba,abab},後綴集是{a,ba,aba,baba},兩者最長重複子串是aba,k=3;
圖解
發現 \(C\) 和 \(D\) 不匹配 \(j\) 位前面的子串是 \(ABA\) ,該子串的前綴集是\({A,AB}\),後綴集是\({A,BA}\),最大的重複子串是\(A\),只有\(1\)個字符,因此j移到 \(k\) 即第1位
同理
在 \(j\) 位的時候,\(j\)前面的子串是\(ABCAB\),前綴集是\({A,AB,ABC,ABCA},\)後綴集是\({B,AB,CAB,BCAB}\),最大重複子串是 \(AB\),個數是\(2\)個字符,所以\(j\) 移到 \(k\) 即第2位。
匹配時的代碼
void pre(){ for(int i = 2,j = 0;i <= m; i++){ while(j > 0 && b[i] != b[j + 1])j = P[j];//匹配失敗,退步 if(b[i] == b[j + 1]) j++;//匹配成功,繼續匹配 P[i] = j; } }
怎麼求這些 \(k\) 呢??
由於在 \(P\) 的每個位置均可能發生不匹配,因此咱們要計算每個位置 \(j\) 對應的 \(k\) ,用一個\(P\)數組保存(B[j] = k)當 A(主串)[i] != B[ j ]時,\(j\) 指針的下一個位置,由於下標從0開始的,\(k\) 值實際是 \(j\) 位前的子串的最大重複子串的長度
\(P[j]\) 的值(也就是\(k\))表示,當\(B[j] != A[i]\)時,\(j\) 指針的下一步移動位置。
咱們用遞推的思想
對\(B = 「ababacb」\)預處理,咱們假設已經求出了\(P[1],P[2],P[3],P[4],\)求\(P[5],P[6]\)
1 2 3 4 5 6 7 B = a b a b a c b P = 0 0 1 2 ? ? ?
很顯然$ P[5] = P[4] + 1\(,由於\)P[4]\(能夠知道\)B[1……2]\(已經和\)B[3……4]$相等了,如今又有 \(B[3] = B[5]\),因此\(P[5]\)能夠用\(P[4]\)加一個字符獲得因此若是在匹配過程當中在\(P[5]\)位置不一樣的時候,直接把\(j\)移到4(P[5] + 1)的位置進行比較
而接着看\(P[6]\) ,它顯然不是\(P[5] + 1\),由於\(P[P[5] + 1] != B[6]\)那麼就要考慮「退一步」,將\(j\)退到\(P[P[3] + 1]\)與A[i]比較,還不匹配,再退發現爲\(P[1] = 0\),stop
看清查找子串時,在主串中能不能交叉,舉個例子:A = 'aaaaaa',B = 'aa',若是不能交叉,匹配完成就直接返回 \(j = 0\) ,若是容許在主串中重複時,若是返回 \(j = 0\) ,就會漏掉連續的\(2,3|4,5\)位置的子串aa;因此應該返回 \(j = P[j]\) ;
int ans = 0,j = 0; for(int i = 1;i <= n; i++){ while(j > 0 && a[i] != b[j + 1])j = P[j]; if(a[i] == b[j + 1])j++; if(j == m){ ans++,j = 0; } } return ans;
/* work by:Ariel */ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int A = 1e3 + 2; int P[A], n, m; char a[A], b[A]; void pre(){ P[1] = 0; int j = 0; for(int i = 1;i < m; i++){ while(j > 0 && b[i + 1] != b[j + 1])j = P[j]; if(b[i + 1] == b[j + 1]) j++; P[i + 1] = j; } } int kmp(){ int ans = 0,j = 0; for(int i = 0;i < n; i++){ while(j > 0 && a[i + 1] != b[j + 1])j = P[j]; if(a[i + 1] == b[j + 1])j++; if(j == m){ ans++; j = 0; } } return ans; } int main(){ while(cin >> a + 1){ if (a[1] == '#')break; scanf("%s",b + 1); m = strlen(b + 1); n = strlen(a + 1); pre(); printf("%d\n",kmp()); } }
hash能夠水??
solution:
\(KMP\)一個簡單的應用,要求主串中最多子串的個數,聯想 \(KMP\) 自我匹配函數
不難發現若是\(n % (n - p[n]) == 0\)那就證實有子串,子串長度爲\(n - p[n]\),因此最多子串個數爲\(n / (n - p[n])\)
而後就套模板了
/* work by:Ariel */ #include <iostream> #include <cstdio> #include <cstring> #include <string> using namespace std; const int A = 1e6 + 5; const int inf = 0x3f3f3f3f; int p[A],n; char a[A]; void pre(){ p[1] = 0; for(int i = 1,j = 0;i < n; i++){ while(j > 0 && a[i + 1] != a[j + 1])j = p[j]; if(a[i + 1] == a[j + 1])j++; p[i + 1] = j; } } int main(){ while(1){ scanf("%s",a + 1); if(a[1] == '.')break; n = strlen(a + 1); pre(); if(n % (n - p[n]) == 0) printf("%d\n",n / (n - p[n])); else printf("1\n"); } }
solution:
結論題
\(ans = n - p[n]\)
/* work by:Ariel */ #include <iostream> #include <cstdio> #include <cstring> using namespace std; const int D = 1e6 + 5; char a[D]; int p[D], n; void pre(){ p[1] = 0; for(int i = 1,j = 0;i < n; i++){ while(j > 0 && a[i + 1] != a[j + 1])j = p[j]; if(a[i + 1] == a[j + 1]) j++; p[i + 1] = j; } } int main(){ scanf("%d%s", &n,a + 1); pre(); printf("%d",n - p[n]); }