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;
}
posted @ 2021-01-15 21:06  传说中的水牛  阅读(90)  评论(0编辑  收藏  举报