KMP算法

字符串匹配一般有两种常见的算法,BF(Brute Force)算法和KMP算法,下面分别说明一下,假定目标串S,长度为n,模式串P,长度为m
BF算法是最直观的算法,从目标串S的起点0到n-m,依次遍历
伪代码如下
for i <- 0 to n-m
    j <- 0//每次P的指针j回溯到起点,S的指针i加一
    while j < m   
        if S[i+j] = P[j]
            j++
        else
            break
    if j = m
        return i
return -1
BMP算法利用了模式串P自身的一些属性,假定S[i,  i+k-1] = P[0, k-1],但是S[i+k]!=P[k],我们可以找到k',k'满足以下条件
1.P[0 ,k'-1] = P[k-k', k-1]
2.k'是所有满足条件1中最大的
然后把整个模式串P之间向右平移k-k'次,也就是把P[k']移动到P[k]的位置,如图所示
 
显然这么做的话,P[0, k'-1]与S[i+k-k' ,i+k-1]之间已经匹配好了,我们只需要比较P[k']与S[k]之间的是否相等。
等等,是不是落下了什么,正确的匹配会不会发生在模式串P向右平移[1, k-k'-1]之间的某次呢?不妨假设向右平移了c次,c属于上述区间,如下图
 
若是发生了正确匹配,必然有P[0, k-c-1] = S[i+c , i+k-1] = P[c+1, k]。另外k-c-1 > k'-1,显然违反了k'的性质2,假设不成立,也就是说正确的匹配不会发生在模式串P向右平移[1, k-k'-1]之间的某次,我们可以放心大胆的平移k-k'了。
 
在大多数经典论述中,上面所说的k'就是next[k],接下来就该考虑如何求解next[k]了,根据我看到的资料,关于next[k]的解法应该是有两种,区别在于是否考虑P[k]和P[k']是否相同,先说不考虑是否相同的情况。首先,next[0] = -1,表示从P的头部开始比较,假定已知k' = next[k],则P[0, k'-1] = P[k-k', k-1],要求next[k+1]
1.如果P[k'] = P[k],则P[0, k'] = P[k-k', k],next[k+1] = next[k] = k'
2.如果P[k'] != P[k],相当于模式串P自己与自己比对,k <- next[k],然后重复上述过程
附代码
void getNext(char *p, int *next)
{
    next[0] = -1;
    int j = 0;
    int k = -1;
    while (j < strlen(p))
    {
        if(k == -1 || p[k] == p[j])
        {
            k++;
            j++;
            next[j] = k;
            /*if(p[k] == p[j])
                next[j] = next[k];
            else
                next[j] = k;*/
        }
        else
            k = next[k];
    }

}

int KMP(char *s, char *p)
{
    int next[20];
    getNext(p, next);
    int i = 0, j = 0;
    while (i < strlen(s))
    {
        if(j==-1 || s[i]==p[j])
        {
            i++;
            j++;
        }
        else
        {
            j = next[j];
        }
        if(j == strlen(p))
            return i-j;
    }
    return -1;
}
第二种算法是考虑了P[k]和P[k']是否相同,对于按照第一种算法算出k',如果P[k]和P[k']不相等,和第一种完全相同;否则k' <- next[k'],注意这句话中的隐含操作,把next[k']赋给k',那么next[k']中存储的又是什么呢,实际上是一种递归操作,找出最大的某下标d,使得P[d]!=P[k],而P[k]=P[k']=P[next[k']]=......P[pre_d],其中next[pre_d]=d,结果就是next[k] = next[k']=...=next[pre_d]=next[d],即把next[d]的值赋给其他,再次强调此时P[d]!=P[k]。可以直观地感觉出这种算法更加高效,因为如果P[k] = P[k'],则P[k'] != S[i+k],模式串仍然需要继续向右平移,而第二种算法一步到位。区别如表格所示
 
getNext的函数也改为如下形式
void getNext(char *p, int *next)
{
    next[0] = -1;
    int j = 0;
    int k = -1;
    while (j < strlen(p))
    {
        if(k == -1 || p[k] == p[j])
        {
            k++;
            j++;
            //next[j] = k;
            if(p[k] == p[j])
                next[j] = next[k];
            else
                next[j] = k;
        }
        else
            k = next[k];
    }

}
而匹配算法int KMP(char *s, char *p)与原来的完全相同。
原文地址:https://www.cnblogs.com/rain-lei/p/3632202.html