KMP算法的数学原理(优化版)

简介: KMP算法

对于一个有限自动机M,它是一个5元组(S,s₀,A,Σ,δ),S是有限状态集,s₀是初始状态(x₀∈X),A是可接受状态集(A⊆X),∑是有限输入表,δ是状态转移函数(从S×Σ到S的映射)。假定有一个模式串p="abaabcb"(长度m),待匹配字符串s="abaabaabcb"(长度n),当第5个字符'c'匹配失败时,寻常的做法是将p的索引回退到0,s的索引回退到1,再重新进行匹配。观察s与p得知:p0...4==s0...4,p0...1==p3...4=="ab",当s5与p5无法匹配时,可以尝试判断s5==p2是否成立,若成立,由前面的推论可知p0...1,2==s3...4,5,所以第5个字符匹配失败时,可以将p的索引回退到2继续进行比较,这样就无需变动s的索引,节约了计算时间,所以只要能够为状态机设计出合理的状态转移函数,就能够加速字符串的匹配。

更一般化情况下,对于模式串p0...m-1,待匹配字符串s0...n-1,对任意i∈0,m-1,j∈0,n-1,有:i,j=δ(i,pj) ( i 为状态机当前状态索引,j 为 s 的索引)。对于δ函数,当循环输入一个字符 pj 时有两种结果,即匹配成功和匹配失败。若匹配成功,i 向后移一位,继续与pj+1进行比较;若匹配失败,则需要将 i 进行跳转,原因后面会解释,这里令 i 的跳转表为 next0...m-1,每次跳转后需重新比较pi与sj,直到它们相等或者i==0时终止跳转,最后再进行一次比较,若相等则 i 可以向后移一位继续与 sj+1比较,伪代码如下:

delta(p,s,next,i,j)
    while i>0 and p[i]!=s[j]
        i=next[i]
    if p[i]==s[j]
        i=i+1
    return i
kmp_search(p,s,next)
    m=p.length
    n=s.length
    i,j=0
    while i<m and j<n
        i=delta(p,s,next,i,j)
        j=j+1
    if i==m
        return j-m
    return -1

前面的模式串p="abaabcb"在第5个字符匹配失败时,因为有p0...4==s0...4,p0...1==p3...4==ab,所以 i 可以回退到2继续进行匹配,这里的 "ab" 我称为p0...4和pk...5的最长公共前缀,其长度记为 π,满足:

π[i] = max{ k : p[0...k-1]==p[i-k...i-1] ∧ k < i }

由上式可推 πi+1=max{k:p0...k-1==p(i+1)-k...(i+1)-1∧k<(i+1)},π0=0,令 πi=x:

1)当pi==px时,总有 p0...x-1px==pi-x...i-1pi,即p0...(x+1)-1==p(i+1)-(x+1)...(i+1)-1,可得πi+1==x+1= =πi+1,因此,对任意pi==p[πi],满足递推式:πi+1==πi+1。

2)当pi !=px时,p0...x-1px==pi-x...i-1pi 显然不成立,那么有没有更短的长度为y(y<x)的公共前缀使 p0...y-1py ==pi-y...i-1 成立呢?这里我同样可以对 px 进行状态转移,令y=πx,由于y是x位置的最长公共前缀的长度,所以有 p0...y-1 ==px-y...x-1,又p0...y-1是p0...x-1的最长前缀,所以p0...y-1也是pi-x...i-1的最长前缀,因此满足:πi+1=πx。

从上面的结论来看,π数组跟next数组是有紧密联系的,它们都完成匹配过程中的状态转移,但是却有些细微的区别,不少网络平台上分享的KMP算法在我看来都是有瑕疵的。考虑这样一种情况,在 π 数组已经计算好的前提下,当pi!=sj,需要将 i 移至 πi,令 k=πi,若 pk==pi,那么再比较pk与sj是没有意义的,因此将这样的情况迭代优化后,就能得到 next 数组,满足:

公式.png
伪代码如下:

compute_next(p,next)
    next[0]=0
    k=0
    m=p.length
    for i = 1 to 
        if p[i]==p[k]
            next[i]=next[k]
            k=k+1
        else
            next[i]=k
            while k>0
                k=next[k]
                if(p[i]==p[k])
                    k=k+1
                    goto out
            <out>

分析伪代码不难得知该算法的时间复杂度是O(m+n),以下是C语言实现的KMP算法:

#include <string.h>

void compute_next(const char* p, int m, int next[]) {
   
    next[0] = 0;
    int k = 0;
    for (int i = 1; i < m; ++i) {
   
        if (p[i] == p[k]) {
   
            next[i] = next[k];
            ++k;
        } else {
   
            next[i] = k;
            while (k > 0) {
   
                k = next[k];
                if (p[i] == p[k]) {
   
                    ++k;
                    break;
                }
            }
        }
    }
}

int delta(const char* p, const char* s, int next[], int i, int j) {
   
    while (i > 0 && p[i] != s[j]) {
   
        i = next[i];
    }
    if (p[i] == s[j]) {
   
        ++i;
    }
    return i;
}

int kmp_search(const char* p, const char* s, int m, int n, int next[]) {
   
    int i = 0, j = 0;
    for (; i < m && j < n; ++j) {
   
        i = delta(p, s, next, i, j);
    }
    return i == m ? j - m : -1;
}

delta函数可以合并到kmp_search函数进行简化,如下:

void compute_next(const char* p, int m, int next[]) {
   ...}

int kmp_search(const char* p, const char* s, int m, int n, int next[]) {
   
    int i = 0, j = 0;
    for (; i < m && j < n; ++j) {
   
        while (i > 0 && p[i] != s[j]) {
   
            i = next[i];
        }
        if (p[i] == s[j]) {
   
            ++i;
        }
    }
    return i == m ? j - m : -1;
}

测试用例:

int main(int argc, char** argv) {
   
    const char* testStrings[][2] = {
   
        {
   "tencent", "encentencentabcskf"},      //true
        {
   "alibaba", "ajsdkalibalibabisk"},      //false
        {
   "baidu", "baibai.www.baidu.com"},      //true
        {
   "bytedance", "ajbytedadanceaaa"},      //false
        {
   "google","googoelglegooglegooo"},      //true
        {
   "microsoft","microsofmicrosofp"}       //false
        };
    int count = sizeof(testStrings) / sizeof(testStrings[0]);
    const char *p, *s;
    int m, n;
    for (int i = 0; i < count; ++i) {
   
        p = testStrings[i][0];
        s = testStrings[i][1];
        m = strlen(p);
        n = strlen(s);
        int next[m];
        compute_next(p, m, next);
        int ret = kmp_search(p, s, m, n, next);
        if (ret != -1)
            printf("模式串'%s'移 %d 位匹配'%s'成功\n", p, ret, s);
        else
            printf("模式串'%s'与'%s'匹配失败\n", p, s);
    }
    return 0;
}
目录
相关文章
|
11天前
|
算法 容器
令牌桶算法原理及实现,图文详解
本文介绍令牌桶算法,一种常用的限流策略,通过恒定速率放入令牌,控制高并发场景下的流量,确保系统稳定运行。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
令牌桶算法原理及实现,图文详解
|
20天前
|
负载均衡 算法 应用服务中间件
5大负载均衡算法及原理,图解易懂!
本文详细介绍负载均衡的5大核心算法:轮询、加权轮询、随机、最少连接和源地址散列,帮助你深入理解分布式架构中的关键技术。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
5大负载均衡算法及原理,图解易懂!
|
13天前
|
算法 调度
基于遗传模拟退火混合优化算法的车间作业最优调度matlab仿真,输出甘特图
车间作业调度问题(JSSP)通过遗传算法(GA)和模拟退火算法(SA)优化多个作业在并行工作中心上的加工顺序和时间,以最小化总完成时间和机器闲置时间。MATLAB2022a版本运行测试,展示了有效性和可行性。核心程序采用作业列表表示法,结合遗传操作和模拟退火过程,提高算法性能。
|
13天前
|
人工智能 算法 大数据
Linux内核中的调度算法演变:从O(1)到CFS的优化之旅###
本文深入探讨了Linux操作系统内核中进程调度算法的发展历程,聚焦于O(1)调度器向完全公平调度器(CFS)的转变。不同于传统摘要对研究背景、方法、结果和结论的概述,本文创新性地采用“技术演进时间线”的形式,简明扼要地勾勒出这一转变背后的关键技术里程碑,旨在为读者提供一个清晰的历史脉络,引领其深入了解Linux调度机制的革新之路。 ###
|
24天前
|
人工智能 算法 数据安全/隐私保护
基于遗传优化的SVD水印嵌入提取算法matlab仿真
该算法基于遗传优化的SVD水印嵌入与提取技术,通过遗传算法优化水印嵌入参数,提高水印的鲁棒性和隐蔽性。在MATLAB2022a环境下测试,展示了优化前后的性能对比及不同干扰下的水印提取效果。核心程序实现了SVD分解、遗传算法流程及其参数优化,有效提升了水印技术的应用价值。
|
23天前
|
存储 缓存 算法
优化轮询算法以提高资源分配的效率
【10月更文挑战第13天】通过以上这些优化措施,可以在一定程度上提高轮询算法的资源分配效率,使其更好地适应不同的应用场景和需求。但需要注意的是,优化策略的选择和实施需要根据具体情况进行详细的分析和评估,以确保优化效果的最大化。
|
24天前
|
并行计算 算法 IDE
【灵码助力Cuda算法分析】分析共享内存的矩阵乘法优化
本文介绍了如何利用通义灵码在Visual Studio 2022中对基于CUDA的共享内存矩阵乘法优化代码进行深入分析。文章从整体程序结构入手,逐步深入到线程调度、矩阵分块、循环展开等关键细节,最后通过带入具体值的方式进一步解析复杂循环逻辑,展示了通义灵码在辅助理解和优化CUDA编程中的强大功能。
|
24天前
|
存储 缓存 算法
前端算法:优化与实战技巧的深度探索
【10月更文挑战第21天】前端算法:优化与实战技巧的深度探索
20 1
|
27天前
|
算法 安全 数据安全/隐私保护
基于game-based算法的动态频谱访问matlab仿真
本算法展示了在认知无线电网络中,通过游戏理论优化动态频谱访问,提高频谱利用率和物理层安全性。程序运行效果包括负载因子、传输功率、信噪比对用户效用和保密率的影响分析。软件版本:Matlab 2022a。完整代码包含详细中文注释和操作视频。
|
12天前
|
算法 数据挖掘 数据安全/隐私保护
基于FCM模糊聚类算法的图像分割matlab仿真
本项目展示了基于模糊C均值(FCM)算法的图像分割技术。算法运行效果良好,无水印。使用MATLAB 2022a开发,提供完整代码及中文注释,附带操作步骤视频。FCM算法通过隶属度矩阵和聚类中心矩阵实现图像分割,适用于灰度和彩色图像,广泛应用于医学影像、遥感图像等领域。