KMP

KMP

首先有暴力算法:

int findPos(char S[], char T[]) {
    int lens = strlen(S), lent = strlen(T), i = 0, j = 0;
    while(i<lens && j<lent) {
        if (S[i] == T[j])
            i++,j++;
        else
            i = i -j + 1,j = 0;    // i, j均移动
  }
  return (j >= lent ? i - lent : -1);
}

然后对暴力算法进行优化:

只移动j,而不移动i。

对于j的移动,j应该如何移动?

当前的\(s[i] \not ={ t[j]}\),如果对于t串,其t[0~j-1]有着相同的前后缀,那么,当\(s[i] \not ={ t[j]}\)时,j最佳的移动方法就是去与后缀相同的前缀的下一个字符处。

由此,我们想知道,对于每一次\(s[i] \not ={ t[j]}\),其前缀的下一处是什么。即nxt[j]。

对于nxt数组,求nxt[j]也就是对t[0~j-1]的最长相同前后缀的长度,那么问题就是,如何快速地得到最长相同前后缀的长度。

下面求nxt数组:

考虑我们已经得到了nxt[0~i],要求nxt[i+1]。

根据定义,nxt[i+1]就是t[0~i]的最长相同前后缀的长度。有下图:

求nxt代码:

void CalcNext(char T[],int next[]) {
  next[0] = -1, next[1] = 0;
  for (int i = 1;i < (int)(strlen(T) - 1);i++) {
        int k = next[i];
        while (k != -1 && T[i] != T[k])
            k = next[k];
        next[i + 1] = k + 1;
  }
}

由此,我们已经得到了KMP:

int findPos_kmp(char S[], char T[], int next[]) {
    int lens = strlen(S), lent = strlen(T), i = 0, j = 0;
    while(i<lens && j<lent) {
        if(j == -1 || S[i] == T[j])
            i++,j++;
        else
            j = next[j];   // 只移动j,而不移动i.
  }
  return (j >= lent ? i - lent : -1);
}

可选优化:

我们知道,对于最大相同前后缀的后缀的后一个字符,如果匹配失败,显然会找到前缀的后一个字符,再进行判断,如果前缀的后一个字符和后缀的后一个字符相等的话,那从后缀的后一个字符到前缀的后一个字符的操作显然无意义,肯定不相等。于是,我们可以判断后缀的后一个字符和前缀的后一个字符是否相等,如果相等,就让后缀的后一个字符的nxt等于前缀的后一个字符的nxt。

void CalcNextVal(char T[],int next[],int nextVal[]) {
    int len = strlen(T);
    for (int i = 0;i < len;i++)
        nextVal[i] = next[i];
    for (int i = 1;i < len;i++) {
        int k = nextVal[i]; // 前缀的后一个字符。
        if (T[i] == T[k])
            nextVal[i] = nextVal[k];  // 后缀的后一个字符的nxt等于前缀的后一个字符的nxt
    }
}
posted @ 2022-10-18 19:36  Uzhia  阅读(30)  评论(0)    收藏  举报