kmp算法
kmp算是是字符串查找算法。要理解它,先介绍一下暴力解法
int indexOf(string str, string pat){
int sLen = str.size();
int pLen = pat.size();
for (int i = 0; i <= sLen - pLen; i++){
int j;
for (j = 0; j < pLen; j++){
if (pat[j] != str[i+j]) break;
}
if (j == pLen) return i;
}
return -1;
}
这个算法有点浪费,比如str="aaaab",pat="aaab"时,当str[3] != pat[3]的时候。这个时候是不需要再从i=1时再从j=0开始一一比较的。
怎么将str="aaaab",pat="aaab"这个特例一般化?
kmp(Donald Knuth、Vaughan Pratt、James H. Morris)三位大佬想了一个办法。
即:如果在字符串不匹配的时候,利用预先处理好的数据,i不回退,直接设置j的值进行比较。如图所示
所以,可以对p进行预处理。(因为p的A~D段 与 s的B~C段 是完全相等的)处理后,查找伪代码如下
int indexOf(string str, string pat){
int sLen = str.size();
int pLen = pat.size();
int j = 0,i=0;
while(i<sLen){
if ( str[i] == str[j] ){
i++;j++;
if ( j == pLen ) return i-pLen;
}else{
j =//已知的 j前面的x个字符串跟 i前面x个字符串相等的位置 的 x
}
}
return -1;
}
在C点,需要预先处理好什么呢?
答:p的C点前面?个字符串(从左到右不含C点的字符) 与p从开始处相匹配?最长是?个字符。//也即是匹配失效时j的值
而p的每一个字符(除前1个)都是C点,即都有一个值。一般叫next数组
怎么求这个next的值,最简单就是两层for循环即可。经过优化后是不需要两层for的
vector<int> GenNext(string p) {
vector<int> next(p.size(),-1);
int k = -1;// k=next[i] 的值,意义为:在 i前面长度为k的字符串 与 以0为开头长度为k的字符串 相等。
//这样的意思是:当 str的?点 与p 在 i点 不相等时。我就可以肯定,字符串0~k也肯定跟 str的?点不相等的。
int i=0;
while(i<p.size()-1){
while(k>=0 && p[i] != p[k] ) k = next[k]; //因为k会被 else next[i] = k; 污染,这个时候k就不是这个 (意义为:在 i前面长度为k的字符串 与 以0为开头长度为k的字符串 相等 )了,所以要找到当前i点的 k值
if ( p[++i] == p[++k] ) next[i] = next[k]; //当 str的?点 与p 在 i点 不相等时。我就可以肯定,字符串0~k也肯定跟 str的?点不相等的。
else next[i] = k; //当 str的?点 与p 在 i点 不相等时。那就去对比下k上的点,因为k上的点跟当前i点不相等。
}
return next;
}
或者较易理解版
vector<int> GenPrefixArr(string &s) {
int len = s.size();
vector<int> ret(len);
for(int i=1,j=0;i<len;++i){
while(j>0&&s[i]!=s[j]) j = ret[j-1];
if(s[i]==s[j]) j++;
ret[i] = j;
}
return ret;
}
附 poj2406 ac代码
#define MAXN 1000005
char szStr[MAXN];
int nxt[MAXN]; //这个表示了,前缀相同的长度,所以只需要观察最后一个就可以了。而且下标是从1开始。
//此处的nxt 并不代表当失配时j往前跳的长度
void Getnxt() {
int i = 0, j = -1, len = strlen(szStr);
nxt[0] = -1;
while (i < len) {
if (j == -1 || szStr[i] == szStr[j]) nxt[++i] = ++j;
else j = nxt[j];
}
}
int main() {
while (scanf("%s", szStr) != EOF && szStr[0] != '.') {
Getnxt();
int len = strlen(szStr);
if (len % (len - nxt[len]) == 0) printf("%d\n", len / (len - nxt[len]));
else printf("1\n");
}
return 0;
}