字符串匹配是指一类在T文本串中查找P模式串的过程=-=
一、暴力匹配
就是扫描T串中所有字符,对第i个字符开始的strlen(p)个字符都尝试与P串进行匹配,一旦失败,i++并继续进行这样的匹配直至匹配成功或i > strlen(t)(即匹配失败)
很显然,这样做的时间复杂度是O(|P||T|),对于特别构造的数据如T:AAAAAAAAAAAAAAAAAAAAB P:AAAAB 是需要大量时间的。
而想要跑得更快的话…这就需要我们的KMP算法上场了!
二、KMP算法
2.1 最长公共前后缀
在此之前,需要先引入一个新概念:最长公共前后缀 意思也比较好理解,就是字符串中最长的前缀和后缀公共(相同的)部分
其中要注意的是:前缀不能包含最后一个字符,后缀不能包含第一个字符,即对于单个字符的字符串,最长公共前后缀 = 0
2.2KMP
我们定义D[i]是从p[0] - p[i] 即前i+1个字符的最长公共前后缀
T串和P串均同时从头开始匹配【为方便描述,T串下标为i, P串下标为j】
1.进行匹配,如果当前匹配成功(T[i] == p[j]),继续匹配下一个
2.如果此时j >= strlen(p) 则匹配成功(退出循环或者继续在T中找下一个P,依题目而定)
3.如果匹配失败,如果j > 0令j = D[j-1], 返回第一步。否则i++,返回第一步。
最关键的地方:当T串与P串匹配成功一些字符【或者一开始就匹配失败了】后,为何在匹配失败后令j = D[j-1]
D[j-1]指p[0]-p[j-1]间最长公共前后缀的长度,假设D[j-1] = x,则说明在这j个字符中最后x个字符和前面x个字符完全相同
同时T串和P串同时匹配成功了前j个字符,故T串中此时i左侧也有同样的j个字符,即前x个字符和后x个字符完全相同
故而前x个字符可以直接与T串中后x个字符进行匹配即可以令j = D[j-1],即j = x, 此时从p[0] - p[x-1]共有x个字符,这x个字符已经同T串中T[i]左侧的i个字符进了匹配。
这就是KMP算法的全过程,总的时间复杂度是O(|P| + |T|)
完了吗?还有一个问题… 我们需要预先处理最长公共前后缀数组D
2.3 求解最长公共前后缀数组
事实上,求解最长公共前后缀的方法,也是在进行KMP的过程(KMP算法中, 对长度为strlen(p)的字符串,如果只进行一次匹配,只需要使用D[0] - D[strlen(p)-2]这些值,也即利用所有下标小于最后一个字符所在位置的下标的最长公共前后缀的长度值)
所以在求解D[i]时,我们只需要参照KMP的方法,同时利用已经求得的D[0] - D[i-1]即可求得D[i]
我们对i = 1, j = 0的两个P串进行KMP式匹配(i为下标的串从p[1] - p[strlen(p)-1], j为下标的串从p[0] - p[strlen(p)-2] 即Pi串不包括第一个字符, Pj串不包括最后一个字符,以此进行前后缀的匹配)
首先显而易见的 D[0] = 0
1.如果匹配成功,D[i++] = ++j
2.如果匹配失败,且j > 0,令j = D[j-1],返回第一步。否则i++,返回第一步。(这里为什么令j = D[j-1]和上面是同样的道理)
循环往复直至i == strlen(p)就已经对D完成初始化了。
完整代码:
1 #include<bits/stdc++.h> 2 using namespace std; 3 4 const int maxn = 1e6 + 5; 5 string p; 6 string t; 7 int d[maxn]; 8 9 void calc_d(string p, int len){ 10 int i = 1, j = 0; 11 while(i < len){ 12 if(p[i] == p[j]) 13 d[i++] = ++j; 14 else{ 15 if(j > 0) 16 j = d[j-1]; 17 else 18 i++; 19 } 20 } 21 } 22 23 int main(){ 24 cin>>t>>p; 25 calc_d(p, p.size()); 26 27 28 int i = 0, j = 0; 29 while(i < t.size()){ 30 if(t[i] == p[j]) 31 i++, j++; 32 else{ 33 if(j > 0) 34 j = d[j-1]; 35 else 36 i++; 37 } 38 if(j == p.size()){ 39 cout<<i-p.size()+1<<endl; 40 j = d[j-1]; 41 } 42 } 43 44 for(int i = 0; i < p.size(); i++) 45 cout<<d[i]<<" "; 46 47 /*if(j != p.size()) printf("NOTFOUND"); 48 else printf("The position is %d", i-p.size());*/ 49 return 0; 50 }
这里的代码查找T串中所有的P串, 所以在从查找成功后再让j = D[j-1]即可。