KMP字符串匹配算法

本篇是小编自学KMP算法时的见解和笔记,有不好的地方欢迎指出!

一、解决的问题:

就是解决文本串的匹配问题。

二、几个概念:

(1)文本串:即原字符串,也就是总字符串。也叫主串

(2)模式串:即目标串,也即是我们要找的字符串。也叫子串

(3)前缀表:用以找到正确匹配过的内容

(4)前缀与后缀,最长相等前后缀

例:(本篇文章全都是以这个例子展开)

                文本串:a  a  b  a  a  b  a  a  f

               模式串: a  a  b  a  a  f

前缀:包含首字母,不包含尾字母的所有子串。比如在这里,a, aa, aab, aaba, aabaa 都是前缀

后缀:只包含尾字母,不包含首字母的子串。,比如在这里,f, af, aaf, baaf, abaaf 都是后缀

 最长相等前后缀:从前到后依次找最长相等的前缀与后缀(实际上就是指相等的那段字符长度)

         这里:从前向后,依次是: a, aa, aab, aaba, aabaa, aabaaf

                他们的前后缀依次是: 0,1 ,0  , 1   ,  2    ,   0

这里解释一下原因:

        对于a,它没有前缀和后缀,所以最长相等前后缀为0

        对于aa,相当于有前缀a,与后缀a,长度为1,所以最长相等前后缀为1

        对于aab,前缀a,aa,与后缀b,ab都不相等,所以最长相等前后缀为0

        对于其他的一样记录

(5)next数组:记录模式串的最长相等前后缀。这里就是next [ 6 ] = { '0', '1' , '0' , '1' , '2' , '0' }

next数组中的值就是下次要比较的模式串的元素的下标

三、next数组

(I)next数组的不同实现方法

(1)第一种方法:next [ 6 ] = { '0', '1' , '0' , '1' , '2' , '0' }

(2)第二种算法:next [ 6 ] = { '-1', '0', '1' , '0' , '1' , '2' } (原数字右移一位)

(3)第三种方法:next [ 6 ] = { '-1', '0' , '-1' , '0' , '1' , '-1' } (原数组全减1)

(II)确定next数组(方法一的代码)

/*
i代表后缀末尾位置,
j代表前缀末尾位置,还代表着i之前,包括i在内的子串的最长相等前后缀的长度
s是模式串
*/
void build_next(int *next, char *s, int n)
{
    int j = 0, i;
    next[0] = 0;
    for (i = 1; i < n; i++) //  注意i从1开始     
    {

        while (j > 0 && s[i] != s[j]) //  j要保证大于0,因为下面有j-1作为数组下标的操作前后缀不相同了
        {
            j = next[j - 1]; // 注意这里,是要找前一位的对应的回退位置
        }
        if (s[i] == s[j]) //  找到相同的前后缀  
        {
            j++;
        }
        next[i] = j; //  将j(前缀的长度)赋给next[i]
    }
}

四、代码实现

(这里写的代码有点麻烦,只是适用于已经知道串的长度的情况,后续会进一步完善)

#include <bits/stdc++.h>
using namespace std;
/*
i代表后缀末尾位置,
j代表前缀末尾位置,还代表着i之前,包括i在内的子串的最长相等前后缀的长度
s是模式串
*/
void build_next(int *next, char *s, int n)
{
    int j = 0, i;
    next[0] = 0;
    for (i = 1; i < n; i++) //  注意i从1开始     
    {

        while (j > 0 && s[i] != s[j]) //  j要保证大于0,因为下面有j-1作为数组下标的操作前后缀不相同了
        {
            j = next[j - 1]; // 注意这里,是要找前一位的对应的回退位置
        }
        if (s[i] == s[j]) //  找到相同的前后缀  
        {
            j++;
        }
        next[i] = j; //  将j(前缀的长度)赋给next[i]
    }
}
void FindStr(char *Str, char *str, int *next, int t, int n)
{
    int i = 0; // 文本串中的指针(下标)
    int j = 0; // 模式串中的指针(下标)
    while (i < t)
    {
        if (Str[i] == str[j]) // 如果字符相匹配,那么比较下一个
        {
            j++;
            i++;
        }
        else // 如果字符不匹配
        {
            if (j > 0) // 如果不是模式串的第一个字符,那么就前移
                j = next[j - 1];
            else
                i++;
        }
        if (j == n) // 如果相等,说明模式串匹配成功,输出文本串中与模式串相同的第一个字符的下标
        {
            cout << i - j << endl;
            break;
        }
    }
    if (j != n) // 如果不相等,说明没匹配成功,输出NOT FOUND
        cout << "NOT FOUND" << endl;
}
int main()
{
    int t, n, i, j;
    cin >> t;
    char Str[t]; // 定义文本串
    for (i = 0; i < t; i++)
        cin >> Str[i];
    cin >> n;
    int next[n];
    char str[n]; // 定义模式串
    for (i = 0; i < n; i++)
        cin >> str[i];
    build_next(next, str, n); // 创建next数组,方便使用
    FindStr(Str, str, next, t, n);
    return 0;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值