算法1:RK算法
算法描述:
(1)计算模式串的Hashcode
方式1:按位相加;
方式2:看成26进制数转化为十进制,如abc = 1x26^2 + 2x26^1 + 4x26^0;
方式2缺点:字符串很长时,对应的十进制数会非常大
(2)主串采用增量计算
例如:主串:abbcefg;模式串bce
第一次计算abb,第二次计算bbc时:新Hahcode = 旧Hashcode - 'a' + 'c'
(3)检查 Hash Collision


//20200306
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN = 1000;
char pattern[MAXN],major[MAXN];
int hashcode(char* arr,int begin,int end){
int sum = 0;
for(int i=begin;i<end;i++){
sum += (int)arr[i] - 'a' + 1; // ASCII of 'a' is 97
}
return sum;
}
int changehashcode(int sumori,int begin,int end){
return sumori - major[begin-1] + major[end-1]; // +96-96 = 0
}
bool hashcollision(int begin,int end){
bool state = false;
for(int i=begin;i<end;i++){
if(major[i] != pattern[i-begin]){
state = true;
break;
}
}
if(state) return true;
else return false;
}
int main(){
int pos = -1;
scanf("%s\n%s",pattern,major);
int lenp = strlen(pattern);
int lenm = strlen(major);
int hashp = hashcode(pattern,0,lenp);
int hashm = hashcode(major,0,lenp);
if(hashm == hashp && !hashcollision(0,lenp)) return 0;
for(int i=1;i<lenm-lenp;i++){
hashm = changehashcode(hashm,i,i+lenp);
if(hashm != hashp) continue;
else if(hashcollision(i,i+lenp)) continue;
else {
pos = i;
break;
}
}
cout<<pos;
return 0;
}
/*---Test---
bce
abbcefgh
//collision
bce
abcbefgh
*/View Code
算法2:KMP算法
1.什么是next数组?
子串s[0...i]的 最长 相等前后缀 的 前缀最后一位 的下标。(也是最长相等前后缀的长度)
什么是前缀?子串s[0...i]中s[0...k](k<i)为前缀,s[...i]为后缀,其中前后缀长度相等。也就是从0开始往后数k个,从最后一个字符开始往前数k个(k<i);(注意k!=i,即不能是子串本身)
举例:s[] = abababc
当i=0时,子串为a,k<i=0中k无法取值,所以next[0] = -1;
当i=1时,子串为ab, k=0 < i=1,前缀a,后缀b,不相等,
所以next[1] = -1;
当i=2时,子串为aba,k=0 < i=2, 前缀a,后缀a,相等,k=0
k=1 < i=2,前缀ab,后缀ba,不相等,
所以最长相等前后缀(即最大的k)为0,所以next[2] = k = 0;
当i=3时,子串为abab,k=0 < i=3,前缀a,后缀b,不相等
k=1 < i=3,前缀ab,后缀ab,相等,k=1
k=2 < i=3,前缀aba,后缀bab,不相等,
所以最长相等前后缀(即最大的k)为1,所以next[3] = k = 1;
当i=4,5,6时,同理;
当i=7(strlen(s) = 7)时,子串为abababc,k=0 < i=7,前缀a,后缀c,不相等。OK,这个时候k就不需要继续往下取值了,因为不可能再相等了,也就是说遇到不相等的情况就可以终止了。【相等k继续递增,不相等k立即终止,next[i]=k-1】;
最终得到next数组为[-1,-1,0,1,2,3,-1]。
2.如何求next数组?(上面举例时采取的求next数组算法比较繁琐,故采用下述递推方法)
我们将在next[i-1]的基础上求next[i]:
假设我们已经求出next[i-1],不妨令它等于j(也就是说s[0...i]的最长相等前后缀的长度为j),那么当我们继续读入一位时,最长相等前后缀的长度 在原来基础上 最多增加1位,也就是说,最长相等前后缀的长度最长为next[i-1]+1;
(理解:字符串xyx的 最长相等前后缀长度为strlen(x),那么xyxa 最长相等前后缀长度 不可能超过strlen(xa) 并且 xyxa 最长相等前后缀长度等于strlen(xa)当且仅当a == y[0])
也就是说,我们判断新读入的这个字符a与y[0]是否相等即可;y[0]是什么?它就是下标j+1所对应的字符,即读入该字符之前的那个字符串 最长相等前后缀的前缀最后一位的下标+1 作为下标对应的字符。
如果 a == y[0],那么next[i] = j+1即可;
如果a != y[0],该怎么办?
字符串waz最长相等前后缀的前缀 最后一位的下标,不妨假设这个下标刚好对应字符a(如果不是我们循环地让j=next[j]即可,直到遇到a或到-1结束),那么字符串z就变成了wa,那么整个字符串wazyuwa中最长相等前后缀的长度就是strlen(wa)。
3.什么是KMP算法?
字符串“自匹配”的过程,也就是在给定字符串的子串s[0,1,2...i]中寻找s[0,1,...k](k<i)和s[.....,i]使得这两个子串完全相等并且k尽可能的大。那么同样的,字符串匹配问题是指给定一个模式串pattern,去和给定的主串text的子串匹配,寻找与模式串相匹配的子串,这个过程和模式串的next数组过程大同小异。
因此,模仿求next数组的过程,我们从主串text和模式串pattern的第一个字符去匹配。如果相等,我们继续匹配text和pattern的第二个字符......如果还没有到pattern的末尾就遇到不相等的情况,这个时候我们要移动pattern位置了,如何一位一位地移动,那么就退化成BF算法了。那么究竟应该移到哪里呢?我们知道,模式串的next[i]数组记录的是模式串的子串pattern[0,1.....,i] 最长相等前后缀 的 前缀最后一位 的下标k,也就是说pattern[0,...k]和pattern[m,....,i](m = i-k)是一样的串。不匹配发生在pattern[i+1]和text[i+1],也就是说pattern[0,...,i]和text[0,....i]都匹配,那么pattern[m,...,i]和text[m,....i]匹配,那么pattern[0,...,k]和text[m,....i]匹配,那我们继续比对pattern[k+1]和text[i+1]是否相等就行了,而k+1就等于next[i+1]。
4.总结
next数组究竟有哪些含义?
(1)子串s[0...i]的 最长相等前后缀 的 前缀最后一位 的下标;
(2)子串s[0...i]的 最长相等前后缀的长度;
(3)主串与模式串匹配时,j+1位不能匹配时,j应该退回的位置。
5.KMP算法对应练习题:洛谷OJ
AC代码:


//20200306
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int MAXN = 1000000;
char pattern[MAXN],text[MAXN];
int next[MAXN];
int value[MAXN];//store the result in LOGU OJ
void getNext(){
int len = strlen(pattern);
next[0] = -1;
int j = next[0];
for(int i=1;i<len;i++){
while(j!=-1 && pattern[i] != pattern[j+1]){
j = next[j];
}
if(pattern[i] == pattern[j+1]) j++;
next[i] = j;
}
}
int KMP(){
getNext();
int lenpt = strlen(pattern);
int lentx = strlen(text);
int j = next[0];
int ans = 0;
for(int i=0;i<lentx;i++){
while(j!=-1 && text[i] != pattern[j+1]){
j = next[j];
}
if(text[i] == pattern[j+1]) j++;
if(j == lenpt - 1){
value[ans++] = i+1-j;
j = next[j];
}
}
return ans;
}
int main(){
scanf("%s\n%s",text,pattern);
int ans = KMP();
for(int i=0;i<ans;i++) cout<<value[i]<<endl;
int len = strlen(pattern);
for(int i=0;i<len;i++) cout<<next[i]+1<<" "; //adjust to LOGU OJ
}View Code
















