基础数据结构与算法之KMP算法-C语言实现

概述

KMP(由Knuth,Morris,Pratt三个人发明)算法,时间复杂度为:
T = O ( n + m ) T=O(n+m) T=O(n+m)

相比于暴力匹配的O(mn)有一定提高。

KMP算法的核心思想就是当发生失配时,则在前面已经匹配的部分中,找到最长的相同前缀,如下图的紫色和绿色部分,那么下次移位时直接将前面的前缀和后面对齐即可,从而不必每次只移动一位。这样string中的指针不会回溯
在这里插入图片描述

构造match数组

为了得知当发生失配时我们要在子串中回退到哪里,我们需要针对pattern进行如下分析处理:

定义match函数构造next表:

m a t c h ( j ) = { 满 足 p 0 . . . p i = p j − i . . . p j 的 最 大 i ( < j ) − 1 , 如 果 这 样 的 i 不 存 在 match(j)= \begin{cases} 满足p_0...p_i=p_{j-i}...p_j的最大i(<j)\\ -1,如果这样的i不存在 \end{cases} match(j)={p0...pi=pji...pji(<j)1i

例如:
在这里插入图片描述
match(j)只与比较小的 pattern子串有关,保存了子串中一首一尾能够匹配的最长子串的信息,我们

将其保存在match[]数组里。

递归地,当计算match[j]时,如果pattern[match[j-1] + 1] == pattern[j],则可以直接得到match[j]=match[j-1]+1在这里插入图片描述

如果没那么幸运pattern[match[j-1] + 1] != pattern[j],也不至于回到起点重开,可以根据match[j-1]的match值,向前找到匹配的部分(下面第一块绿色部分),则对于相同的后半段,最后的紫色部分和最开始的绿色部分一定是匹配的,再利用上面的步骤即可。
在这里插入图片描述
第一个问号处是match[match[j-1]+1],第二个问号处当然是j

KMP算法

有了match[]数组,KMP算法的实现很简单:

定义sp两个指针分别从头开始遍历主串和模式串,当二者都未到达结尾时执行如下循环:

if (str[s] == pattern[p]),说明当前字符匹配,s和p同时向后移动;

else if (p > 0),当前字符失配,且不是模式串的第0个字符,就是下图的平凡情况,则需要将p指针回溯到绿色部分末尾,也就是p-1的match值:p = march[p - 1] + 1;
在这里插入图片描述
否则说明模式串的第一个字符就不匹配++s

代码实现

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

void buildMatch(char const* pattern, int* match) {
    int prev = 0, j = 0;
    int m = strlen(pattern);
    match[0] = -1;
    for (j = 1; j < m; ++j) {
        prev = match[j - 1];
        while ((prev >= 0) && (pattern[prev + 1] != match[j])) {
            //i不断回退
            prev = match[prev];
        }
        if (pattern[j] == pattern[prev + 1]) {
            //pattern[j]==pattern[match[j-1]+1]
            match[j] = prev + 1;
        } else {
            //没有真子串可以匹配
            match[j] = -1;
        }
    }
    return;
}

int KMP(char const* str, char const* pattern) {
    //获取str的长度O(n)
    int n = strlen(str);
    //获取pattern的长度O(m)
    int m = strlen(pattern);
    int s = 0, p = 0;
    if (m > n)
        return -1;
    int* match = (int*)malloc(sizeof(int) * m);
    //构造next表
    buildMatch(pattern, match);
    //都没有触达结尾O(n),s从来没有回退过
    while (s < n && p < m) {
        if (str[s] == pattern[p]) {
            //当前字符匹配
            ++s;
            ++p;
        } else if (p > 0) {
            //当前字符失配,跳转到上一个小子串末尾
            p = match[p - 1] + 1;
        } else {
            //第一个字符就失配
            ++s;
        }
    }

    return (p == m) ? s - m : -1;
}

测试代码

void test_KMP() {
    printf("\n%s\n", __func__);
    char string[] = "This is a simple example";
    char pattern0[] = "simple";
    char pattern1[] = " isa";
    char pattern2[] = "Th";
    char pattern3[] = "e";
    char pattern4[] = "exam";
    char pattern5[] = "sample";
    char* patterns[6] = {pattern0, pattern1, pattern2, pattern3, pattern4, pattern5};
    int p;
    printf("\nstring:%s\n", string);
    for (int i = 0; i < 6; ++i) {
        printf("\npattern%d:%s\n", i, patterns[i]);
        p = KMP(string, patterns[i]);
        if (p != -1) {
            printf("%s\n", string + p);
        } else {
            printf("Not found\n");
        }
    }
    return;
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值