KMP算法[个人理解与笔记]

目录

一.暴力匹配

二.KMP算法

图文与代码:

最大公共元素与next数组:


一.暴力匹配

当我们需要在长串S中,找到我们需要的模式串L。使用暴力匹配的话,需要将整个串不断的回溯,时间复杂度是(m*n)

#include<stdio.h>

int violentmatch(char* ske, char* str) {
    int len1 = strlen(ske);
    int len2 = strlen(str);

    int i = 0, j = 0;
    while (i < len1 && j < len2) {
        if (ske[i] == str[j]) {
            i++;
            j++;
        } else {
            i = i - j + 1;//i需要回溯到主串的第n+1个 n=0;
            j = 0;
        }

    }

    if (j == len2) {
        return i - j;
    } else {
        return -1;
    }

}

二.KMP算法

介绍:KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。

图文与代码:

  • 假设:现在主串S为ABEABDABCE,字串L为ABC

图文流程:

ABEABDABCEA
ABEABDABCEB
ABEABDABCEC(匹配失败)
ABEABDABCE此时如果是暴力算法,将会回溯标红处,重新匹配。但是如果是KMP算法,将会从标蓝处开始。
ABEABDABCEA(匹配失败)
ABEABDABCEA
ABEABDABCEB
ABEABDABCEC(匹配失败)
ABEABDABCEA(匹配失败)
ABEABDABCEA
ABEABDABCEB
ABEABDABCEABC(匹配成功)
  • 我们不难发现,每次匹配的位置并非是暴力算法中的,将主串固定的向右位移一位而是向右位移n位。而这里的n,就是KMP算法的核心。

代码:

  • int KMPalgorithm(char* ske, char* str) {
        int len1 = strlen(ske);
        int len2 = strlen(str);
        int i = 0, j = 0;//i用来记录主串,j用来记录子串
        while (i < len1 && j < len2) {
            if (j==-1&&ske[i] == str[j]) //j==-1做条件是为什么?会在后面说到{
                i++;
                j++;
            } else {
                j = next[j];//前文所述的n就是j-next[j];
            }
        }
        if (j == len2) {
            return i - j;
        } else {
            return -1;
        }
    
    }

最大公共元素与next数组:

最大公共元素的求法是理解KMP算法和求出next数组的关键。

假定:主串S为ABABCDEABABD,字串L为ABABD

求最大公共元素长度:

字串前缀后缀最大公共元素长度
ANULLNULL0
ABAB0
ABAA,ABA,BA1
ABABA,AB,ABAB,AB,BAB2
ABABDA,AB,ABA,ABABD,BD,ABD,BABD0

这时候可以得出:

字串ABABD
最大公共元素长度00120

求next数组:next 数组考虑的是除当前字符外的最长相同前缀后缀,将最大公共元素中求得的值整体右移一位,然后初值赋为-1即可。

字串ABABD
最大公共元素长度-10012

根据next匹配:

主串:ABABCDEABABD

字串:

ABABD

ABABCDEABABDA
ABABCDEABABDAB
ABABCDEABABDABA
ABABCDEABABDABAB

ABABCDEABABD

ABABCDEABABD

ABABD(第五个不匹配时,根据next表可以知道,next[j]=2,j=4,j-next[j]=2,所以将子串右移两位,也就是绿色标记处)

ABABCDEABABDA
.............
ABABCDEABABDABA(第三个不匹配时,next[j]=0,j=3,j-next[j]=3,所以子串右移三位
............
ABABCDEABABDA
ABABCDEABABDAB
ABABCDEABABDABA
ABABCDEABABDABAB
ABABCDEABABDABABD(匹配成功)

next数组的求法:

这段内容是最难以理解的,即使已经弄懂了,本人也无法组织好语言,只好借鉴他人的语言(原文链接https://blog.csdn.net/dark_cy/article/details/88698736),来方便理解并补充内容。

首先是代码:

void Getnext(int next[], String t) {
    int j = 0, k = -1;
    next[0] = -1;
    while (j < t.length - 1) {
        if (k == -1 || t[j] == t[k]) {
            j++;
            k++;
            next[j] = k;
        } else k =
                next[k]; 
    }
}

next求解的三个情况:

  1. 当 j 的值为 0 或 1 的时候,它们的 k 值都为 0,即 next[0] = 0、next[1] =0。但是为了后面 k 值计算的方便,我们将 next[0] 的值设置成 -1。
  2. 当 t[j] == t[k] 的情况在这里插入图片描述观察上图可知,当 t[j] == t[k] 时,必然有"t[0]…t[k-1]" == " t[j-k]…t[j-1]",此时的 k 即是相同子串的长度。因为有"t[0]…t[k-1]" == " t[j-k]…t[j-1]",且 t[j] == t[k],则有"t[0]…t[k]" == " t[j-k]…t[j]",这样也就得出了next[j+1]=k+1。
  3. 当t[j] != t[k] 的情况

在这里插入图片描述t[j+1] 的最大子串的长度为k,(我认为这个k应该就是最大的公共长度)

所以就有 next[j+1] < k,那么求 next[j+1] 就等同于求 t[j] 往前小于 k 个的字符(包括t[j],看上图蓝色框框)与 t[k] 前面的字符(绿色框框)的最长重合串,即 t[j-k+1] ~ t[j] 与 t[0] ~ t[k-1] 的最长重合串(这里所说“最长重合串”实不严谨,但你知道是符合 k 的子串就行…),那么就相当于求 next[k](只不过 t[k] 变成了 t[j],但是 next[k] 的值与 t[k] 无关)所以才有了这句 k =next[k],如果新的一轮循环(这时 k = next[k] ,j 不变)中 t[j] 依然不等于 t[k] ,则说明倒数第二大 t[0~next[k]-1] 也不行,那么 k 会继续被 next[k] 赋值(这就是所谓的 k 回退…),直到找到符合重合的子串或者 k == -1。
 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值