kmp算法笔记与总结

kmp算法笔记与总结

1. 暴力匹配算法

本文参考了一位大佬的文章:把kmp算法解析的非常清楚,大家可以看看。

假设现在我们面临这样一个问题:有一个文本串和模式串,要在文本串中查找模式串的位置,有一种暴力匹配的模式,假设文本串匹配到i位置,模式串匹配到j位置,则有:

  • 如果当前字符匹配成功,这i++,j++,继续匹配下一字符;

  • 如果失败,令i=i-(j-1),j=0。相当于每次匹配失败,i回溯,j被置为。

    理解了暴力模式匹配算法的思想,暴力匹配思想的代码如下;

#include 
using namespace std;
int fun(char *s, char *p)
{
    int slen = strlen(s);
    int plen = strlen(p);
    int i = 0, j = 0;
    while (i < slen && j < plen)
    {
        if (s[i] == p[j])
        {
            i++;
            j++;
        }
        else
        {
            i = i - j + 1;
            j = 0;
        }
    }
    if (j == plen)
    {
        return i - j;
    }
    return -1;
}
int main()
{
    char s[20], p[20];
    scanf("%s %s", s, p);
    int n = fun(s, p);
    cout << n << endl;
    return 0;
}

2. KMP算法

下面先给出KMP的算法流程

  • 假设现在文本串s匹配到i位置,模式串匹配到pj位置

  • 如果j=-1,或者当前字符匹配成功(s[i]==p[j]),都令i++,j++,继续匹配下一个字符;

  • 如果j!=-1,且当前字符匹配失败,则令i不变,j=next/[j]。此举意味着匹配失败时,模式串相对于文本串向右移动了j-next[j]位。

  • 换言之,当匹配失败时,模式串向右移动的位数为:失配字符所在的位置-失配字符对应的next值,即实际的移动位数为:j-next[j],且此值大于1。

    kmp的实现代码如下;

int KmpSearch(char *s, char *p, int *next)
{
    int i = 0;
    int j = 0;
    int sLen = strlen(s);
    int pLen = strlen(p);
    while (i < sLen && j < pLen)
    {
        //①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++
        if (j == -1 || s[i] == p[j])
        {
            i++;
            j++;
        }
        else
        {
            //②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]
            // next[j]即为j所对应的next值
            j = next[j];
        }
    }
    if (j == pLen)
        return i - j;
    else
        return -1;
}

求next数组的代码如下:

void GetNext(char *p, int *next)
{
    int pLen = strlen(p);
    next[0] = -1;
    int k = -1;
    int j = 0;
    while (j < pLen - 1)
    {
        // p[k]表示前缀,p[j]表示后缀
        if (k == -1 || p[j] == p[k])
        {
            ++k;
            ++j;
            next[j] = k;
        }
        else
        {
            k = next[k];
        }
    }
}

下面是完整代码的第一种写法,此种写法没有把求next数组的过程写成函数。

#include 
using namespace std;
int KmpSearch(char *s, char *p, int next[20])
{
    int i = 0;
    int j = 0;
    int sLen = strlen(s);
    int pLen = strlen(p);
    while (i < sLen && j < pLen)
    {
        //①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++
        if (j == -1 || s[i] == p[j])
        {
            i++;
            j++;
        }
        else
        {
            //②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]
            // next[j]即为j所对应的next值
            j = next[j];
        }
    }
    if (j == pLen)
        return i - j;
    else
        return -1;
}
int main()
{
    char s[20], t[20];
    scanf("%s %s", s, t);
    int plen = strlen(t);
    int next[20];
    next[0] = -1;
    int k = -1;
    int j = 0;
    while (j < plen - 1)
    {
        if (k == -1 || t[j] == t[k])
        {
            ++k;
            ++j;
            next[j] = k;
        }
        else
        {
            k = next[k];
        }
    }
    cout << KmpSearch(s, t, next) << endl;
    return 0;
}

下面第二种写法是把求next数组的部分也写成了一个函数,推荐使用这种写法

#include 
using namespace std;
void GetNext(char *p, int *next)
{
    int pLen = strlen(p);
    next[0] = -1;
    int k = -1;
    int j = 0;
    while (j < pLen - 1)
    {
        // p[k]表示前缀,p[j]表示后缀
        if (k == -1 || p[j] == p[k])
        {
            ++k;
            ++j;
            next[j] = k;
        }
        else
        {
            k = next[k];
        }
    }
}
int KmpSearch(char *s, char *p, int *next)
{
    int i = 0;
    int j = 0;
    int sLen = strlen(s);
    int pLen = strlen(p);
    while (i < sLen && j < pLen)
    {
        //①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++
        if (j == -1 || s[i] == p[j])
        {
            i++;
            j++;
        }
        else
        {
            //②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]
            // next[j]即为j所对应的next值
            j = next[j];
        }
    }
    if (j == pLen)
        return i - j;
    else
        return -1;
}
int main()
{
    char s[20], t[20];
    scanf("%s %s", s, t);
    int plen = strlen(t);
    int *p;
    p = (int *)malloc((plen + 1) * sizeof(int));
    GetNext(t, p);
    cout << KmpSearch(s, t, p) << endl;
    return 0;
}

3、求next数组的优化

求next数组的值得过程可以进行一定的优化,总结起来就是,他是在计算出next值的同时,如果a位字符与他next值指向的b位字符相等,则该a位的nextval就指向b位的nextval值,如果不等,则该a位的nextval值就是他自己原来的next值。

求next数组的优化代码如下,又称为求nextval数组:

void GextNextval(char *p, int *next)
{
    int plen = strlen(p);
    int k = -1;
    int j = 0;
    next[0] = -1;
    while (j < plen - 1)
    {
        if (k == -1 || p[k] == p[j])
        {
            k++;
            j++;
            if (p[k] != p[j])
                next[j] = k;
            else
                next[j] = next[k];
        }
        else
        {
            k = next[k];
        }
    }
}

kmp算法总体来说较难理解,同学们还得自己多多研究。

你可能感兴趣的:(c语言,c++,链表)