KMP算法

KMP字符串匹配算法

 算法流程

(1)

  首先,主串"BBC ABCDAB ABCDABCDABDE"的第一个字符与模式串"ABCDABD"的第一个字符,进行比较。因为 B 与 A 不匹配,所以模式串后移一位。

(2)

  因为 B 与 A 又不匹配,模式串再往后移。

(3)

  就这样,直到主串有一个字符,与模式串的第一个字符相同为止。

(4)

  接着比较主串和模式串的下一个字符,还是相同。

(5)

  直到主串有一个字符,与模式串对应的字符不相同为止。

(6)

  这时,最自然的反应是,将模式串整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把"搜索位置"移到已经比较过的位置,重比一遍。

(7)

  一个基本事实是,当空格与 D 不匹配时,你其实是已经知道前面六个字符是"ABCDAB"。KMP 算法的想法是,设法利用这个已知信息,不要把"搜索位置"移回已经比较过的位置,而是继续把它向后移,这样就提高了效率。

(8)

          

(9)怎么做到这一点呢?可以针对模式串,设置一个跳转数组int next[],这个数组是怎么计算出来的,后面再介绍,这里只要会用就可以了。

  已知空格与 D 不匹配时,前面六个字符"ABCDAB"是匹配的。根据跳转数组可知,不匹配处 D 的 next 值为 2,因此接下来从模式串下标为 2 的位置开始匹配。

(10)

  因为空格与 C 不匹配,C 处的 next 值为 0,因此接下来模式串从下标为 0 处开始匹配。

(11)

  因为空格与 A 不匹配,此处 next 值为 -1,表示模式串的第一个字符就不匹配,那么直接往后移一位。

(12)

  逐位比较,直到发现 C 与 D 不匹配。于是,下一步从下标为 2 的地方开始匹配。

(13)

  逐位比较,直到模式串的最后一位,发现完全匹配,于是搜索完成。

next 数组是如何求出的

  next 数组的求解基于“真前缀”和“真后缀”,即next[i]等于P[0]...P[i - 1]最长的相同真前后缀的长度。

    

  1. i = 0,对于模式串的首字符,我们统一为next[0] = -1
  2. i = 1,前面的字符串为A,其最长相同真前后缀长度为 0,即next[1] = 0
  3. i = 2,前面的字符串为AB,其最长相同真前后缀长度为 0,即next[2] = 0
  4. i = 3,前面的字符串为ABC,其最长相同真前后缀长度为 0,即next[3] = 0
  5. i = 4,前面的字符串为ABCD,其最长相同真前后缀长度为 0,即next[4] = 0
  6. i = 5,前面的字符串为ABCDA,其最长相同真前后缀为A,即next[5] = 1
  7. i = 6,前面的字符串为ABCDAB,其最长相同真前后缀为AB,即next[6] = 2
  8. i = 7,前面的字符串为ABCDABD,其最长相同真前后缀长度为 0,即next[7] = 0

 

 

计算next数组:(顺序表的形式)

  

/* P 为模式串,下标从 0 开始 */
void GetNext(string P, int next[])
{
    int p_len = P.size();
    int i = 0;   // P 的下标
    int j = -1;  
    next[0] = -1;
    while (i < p_len)
    {
        if (j == -1 || P[i] == P[j])
        {
            i++;
            j++;
            next[i] = j;
        }
        else
            j = next[j];
    }
}

计算next数组:(链表的形式)

//next数组的计算

void makeNext(PStrString p,int *next){
    int i= 0,k=-1;
    next[0]=-1;//初始化
    while (i<p->n-1)
    {
        while (k>=0&&p->c[i]!=p->c[k])
        {
            k=next[k];
        }
        i++,k++;
        next[i]=k;
    }
}

 

KMP(顺序表形式)

int KMP(string S, string P, int next[])
{
    GetNext(P, next);

    int i = 0;  // S 的下标
    int j = 0;  // P 的下标
    int s_len = S.size();
    int p_len = P.size();

    while (i < s_len && j < p_len) // 因为末尾 '' 的存在,所以不会越界
    {
        if (j == -1 || S[i] == P[j])  // P 的第一个字符不匹配或 S[i] == P[j]
        {
            i++;
            j++;
        }
        else
            j = next[j];  // 当前字符匹配失败,进行跳转
    }

    if (j == p_len)  // 匹配成功
        return i - j;
    
    return -1;
}

 

KMP (链表形式)

int pMath(PSeqString t,PSeqString p,int *next){
    int i,j;
    i =0;j=0;
    while (i<p->n&&j<t->n)
    {
        if(i==-1||p->c[i]==t->c[j]){
            i++;
            j++;
        }else
        {
            i=next[i];
        }
        
    }
    if (i>=p->n)
    {
        return j-p->n+1;
    }else
    {
        return 0;
    }
}

 

因上求缘,果上努力~~~~ 作者:每天卷学习,转载请注明原文链接:https://www.cnblogs.com/BlairGrowing/p/13034930.html

原文地址:https://www.cnblogs.com/BlairGrowing/p/13034930.html