KMP

KMP算法

特別感謝 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;
	}
}

important

怎麼求這些 \(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());
  }
  
}

Power Strings

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");
  }
}

Radio Transmission

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]); 
}
相關文章
相關標籤/搜索