KMP(C语言实现)

字符串模式匹配

问题描述:给定目标串s与模式串t,在主串s中寻找模式串t的过程称为模式匹配(pattern matching)

朴素字符串匹配:Brute-Force算法

算法描述:一种暴力匹配算法,从s的首位开始,逐位与t串匹配;

 设当前从tpos位开始匹配,当失配时便从pos+1位重新匹配。

代码实现:

公共字符串接口:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define L 100010

struct String
{
    int length;
    char* data;
};

typedef struct String* string;
#define newString (string)malloc(sizeof(struct String))

void input(string now)
{
    char str[L];
    scanf("%s", str);

    now->length = (int)strlen(str);
    now->data = (char*)malloc(sizeof(char) * now->length);
    strcpy(now->data, str);
}

BruteForce函数及主函数:

void BruteForce(string s, string t)
{
    int i = 0, j = 0;
    //若s剩余长度不及t剩余长度,则不再有继续匹配的需要
    while(i + t->length - j <= s->length)
        //当前位匹配成功
        if(s->data[i] == t->data[j])
        {
            i++;
            j++;

            //在s中找到了一个完整的t,从pos + 1位继续尝试匹配
            if(j == t->length)
            {
                //输出一个结果
                printf("%d ", i - t->length);

                i -= t->length - 1;
                j = 0;
            }
        }
        //失配则跳回pos + 1
        else
        {
            i -= j - 1;
            j = 0;
        }
}

int main()
{
    string s = newString, t = newString;
    input(s), input(t);
    BruteForce(s, t);
}

时间复杂度分析:最坏时间复杂度:O(s.len × t.len)

这在大数据范围时显然会TLE

引言

不难注意到,当t[j]在s[i]处失配时,若t[0 .. k - 2] ≠ t[1 .. k - 1],因为到t[j]才失配,所以有
s[i - j + 2 .. i - 1] = t[1 .. j - 1],则s[i - j + 2 .. i - 1]  ≠ t[0 .. j - 2],即从pos + 1处开始匹配必然会失配;同理,若t[0 .. k - 3] ≠ t[2 .. k - 1],则s[i - j + 3 .. i - 1]  ≠ t[0 .. j - 3]...依此类推。

也就是说,只有当t[0 .. k - p] = t[p - 1 .. k - 1] (2 ≤ p ≤ k)时,才有可能会匹配成功。如果我们能够推导出在此处失配后,下一次从何处匹配才有可能成功(避免了两处之间的必然会失配的位置),就会显著加快匹配速度。

Knuth-Morris-Pratt(KMP)算法

现在,我们引入数组next[j]来表示当在s[i]t[j]处失配后应将s[i]继续与t[next[j]]匹配。

假设我们知道next[]数组,那么只需将朴素模式匹配算法稍加修改,即在失配时将t[j]返回t[next[j]]并且继续与s[i]进行匹配,而不是返回t[0]s[i - j + 1]处重新匹配。直到j跳回0后仍不匹配才将i + 1进行下一轮匹配。

KMP函数:

void KMP(string s, string t, int* next)
{
    int i = 0, j = 0;
    while(i + t->length - j <= s->length)
        if(s->data[i] == t->data[j])
        {
            i++;
            j++;
            
            if(j == t->length)
            {
                printf("%d ", i - t->length + 1);

                i -= t->length - 1;
                j = 0;
            }
        }
        //还没有到0
        else if(j != 0)
            j = next[j];
        //确定该位无法匹配
        else
            i++;
}

接下来我们需要解决next[]数组的求法,不难看出,next[j]实际上就表示t[0 .. j - 1]的最长相同前后缀的长度。

现尝试找出next[]的递推式

t[j] = t[next[j - 1]],说明t[0 .. j - 1]的最长相同前后缀可以在t[0 .. j - 2]的基础上延长一位,则next[j] = next[j - 1] + 1;由于next[j - 1]已经是t[0 .. j - 2]的最长相同前后缀,也就是说两端不可能再向中间扩增来延长其长度,那么需要以同等路径的next[j]的延长也必不可能成功,即            next[j] ≤ next[j - 1] + 1

t[j] ≠ t[next[j - 1]],说明t[next[j - 1]]与t[j]失配,需要找到t[next[j - 1]]处失配应该返回的位置,这恰好就是next[next[j - 1]]的定义,可以理解为计算next[]的过程实际上就是模式串t与自己匹配的过程。那么next[]的求法本身就和KMP匹配时大同小异:只要不匹配,就不断跳到之前的next[](即迭代过程j = next[j]),直到j = 0

InitNext函数:

void initNext(string t, int* next)
{
    next[0] =  next[1] = 0;

    int i = 2, j = 0;
    /*
    i从2开始是因为若在循环中计算next[1],第一个if语句的比较条件一定是拿t[0]和其本身比较,
    一定相等,而单独一个字符的字符串我们不认为它有相同前后缀(否则适配时将无限从t[0]开始比较,
    导致死循环)。
    */
    while(i < t->length)
        if(t->data[i - 1] == t->data[j])
        {
            next[i] = ++j;
            i++;
        }
        else if(j != 0)
            j = next[j];
        //彻底没有相同前后缀
        else
            next[i++] = 0;
}

时间复杂度分析:O(s.len)

完整程序代码:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#define L 100010

struct String
{
    int length;
    char* data;
};

typedef struct String* string;
#define newString (string)malloc(sizeof(struct String))

void input(string now)
{
    char str[L];
    scanf("%s", str);

    now->length = (int)strlen(str);
    now->data = (char*)malloc(sizeof(char) * now->length);
    strcpy(now->data, str);
}

void initNext(string t, int* next)
{
    next[0] = next[1] = 0;

    int i = 2, j = 0;
    /*
    i从2开始是因为若在循环中计算next[1],第一个if语句的比较条件一定是拿t[0]和其本身比较,
    一定相等,而单独一个字符的字符串我们不认为它有相同前后缀(否则适配时将无限从t[0]开始比较,
    导致死循环)。
    */
    while(i < t->length)
        if(t->data[i - 1] == t->data[j])
        {
            next[i] = ++j;
            i++;
        }
        else if(j != 0)
            j = next[j];
        //彻底没有相同前后缀
        else
            next[i++] = 0;
}

void KMP(string s, string t, int* next)
{
    int i = 0, j = 0;
    while(i + t->length - j <= s->length)
        if(s->data[i] == t->data[j])
        {
            i++;
            j++;

            if(j == t->length)
            {
                printf("%d ", i - t->length);

                i -= t->length - 1;
                j = 0;
            }
        }
        //还没有到0
        else if(j != 0)
            j = next[j];
        //确定该位无法匹配
        else
            i++;
}

int main()
{
    string s = newString, t = newString;
    input(s), input(t);

    int next[L];
    initNext(t, next);
    KMP(s, t, next);
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值