KMP算法原理

2023-10-29

所有下标从0开始

子串的定位操作通常称为串的模式匹配,它求的是子串(或称模式串)在主串中的位置。

前缀:除最后一个字符外,字符串的所有头部子串。
后缀:除第一个字符外,字符串的所有尾部子串。
部分匹配值:字符串的前缀和后缀的最长相等前后缀长度。

字符串 前缀 后缀 部分匹配值
a {} {} 0
ab {a} {b} 0
aba {a, ab} {a, ba} 1
abab {a, ab, aba} {b, ab, bab} 2
ababa {a, ab, aba, abab} {a, ba, aba, baba} 3

暴力匹配O(mn):匹配失败后,模式串指针j回退到初始位置(0),主串指针i回退到本轮匹配初始位置的下一个位置(3 + 1=4)。
KMP算法O(m+n):根据模式串中已成功匹配子串(abcab)的特点,不需要全部回退,i保持不动,j回退到已成功匹配子串的最长相等前缀的下一个位置即可(ab后面的c的位置,即下标2)。因此,我们可以建立一个和模式串等长的数组next,存储j位置匹配失败时j的下一个位置。next[j]的值为pattern[1 ~ j-1]的部分匹配值,即最长相等前缀 或 最长相等后缀 的长度。

采用数学归纳法:

  1. next[0] = -1; // 模式串第0个匹配失败,i不动,赋值j=-1,则++i;++j后等效于模式串第0个字符与主串下一个字符相比。

  2. 设next[i] = j;

    则pattern[0 ~ i-1]中,最长相等前缀为pattern[0 ~ j-1],最长相等后缀为pattern[i-j ~ i-1];

    因此,pattern[0~i]最长相等前后缀 只需比较 pattern[0 ~ j] 和 pattern[i-j ~ i] 即可(最长相等前后缀各向后扩一个)。

    // pattern[0~i] 可能达到的 最长相等前后缀 即为 pattern[0 ~ j] 和 pattern[i-j ~ i],要求pattern[j] == pattern[i]
    // 若pattern[j] != pattern[i],则问题转变为 pattern[0 ~ j]为模式串 和 pattern[i-j ~ i]为主串的 匹配问题
    // (pattern[0 ~ j-1] 和 pattern[i-j ~ i-1]已成功匹配,且pattern[j] != pattern[i],则模式串 指针 应该根据next数组前跳)
    
    while (j !=-1 && pattern[i] != pattern[j])
    	j = next[j]; // j < i,next[i]之前即next[0 ~ i-1]已求得
    next[i + 1] = j + 1;
    

  3. 还有一个问题,text[i]和pattern[j]匹配失败,j=next[j],新的pattern[j]或和老的pattern[j]相等,则又要重新跳转,因此,我们应该保证pattern[j] != pattern[next[j]]

    while (j !=-1 && pattern[i] != pattern[j])
    	j = next[j]; // j < i,next[i]之前即next[0 ~ i-1]已求得
    if (pattern[i + 1] == pattern[j + 1])
        next[i + 1] = next[j + 1]; // 不用递归跳转,因为next是依次加入的,若 pattern[j + 1] == pattern[next[j + 1]],则将跳转使得其不相等,所以pattern[i+1]不可能连续等于pattern[j + 1]和pattern[next[j + 1]]
    else
    	next[i + 1] = j + 1;
    
import java.util.Random;

public class Main {

    // 生成随机字符串
    public static String randomString(String range, int length) {
        Random random=new Random();
        StringBuffer sb=new StringBuffer();
        for (int i = 0; i < length; i++) {
            sb.append(range.charAt(random.nextInt(range.length())));
        }
        return sb.toString();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            String text = randomString("abcdefg", 1000);
            String pattern = "abc";

            int ref = text.indexOf(pattern);
            int out = indexOf(text, pattern);
            int kmp = indexOfByKMP(text, pattern);

            System.out.printf("ref: %10d, out: %10d, kmp: %10d\n", ref, out, kmp);
            assert ref == out;
            assert ref == kmp;
        }
    }

    // 暴力匹配
    public static int indexOf(String text, String pattern) {
        int i = 0, j = 0;
        while (i < text.length() && j < pattern.length()) {
            if (text.charAt(i) == pattern.charAt(j)) {
                ++i;
                ++j;
            } else {
                i = i - j + 1;
                j = 0;
            }
        }
        if (j == pattern.length())
            return i - pattern.length();
        else
            return -1;
    }

    private static int[] getNext(String pattern) {
        int[] next = new int[pattern.length()];

        int i = 0, j = -1;
        next[i] = j;

        while (i < pattern.length() - 1) {
            if (j != -1 && pattern.charAt(i) != pattern.charAt(j))
                j = next[j];
            ++i;
            ++j;
            if (pattern.charAt(i) == pattern.charAt(j))
                next[i] = next[j];
            else
                next[i] = j;
        }

        return next;
    }

    // KMP算法
    public static int indexOfByKMP(String text, String pattern) {
        int[] next = getNext(pattern);

        int i = 0, j = 0;
        while (i < text.length() && j < pattern.length()) {
            if (j == -1 || text.charAt(i) == pattern.charAt(j)) {
                ++i;
                ++j;
            } else {
                j = next[j];
            }
        }
        if (j == pattern.length())
            return i - pattern.length();
        else
            return -1;
    }
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

KMP算法原理 的相关文章

随机推荐

  • verdaccio内网离线搭建npm私有仓库

    使用场景 前端项目的编译运行开发中报下载经常出错 项目场景 通常我们前端项目开发搭建过程中通过npm管理前端js库 新建项目或内网开发过程中经常出现环境搭建的问题 例如常见错误Failed at the chromedriver 2 37
  • QT学习一:利用QT QAxObject读取Excel表格数据的两种方法比较

    目录 QAxObject QVariant 1 逐单元格读取表格内容 2 一次性读取工作表使用范围 利用QT的 QAxObject读取Excel表格数据的两种方法比较 完整的QT源码到此下载 ReadExcel rar 嵌入式文档类资源 C
  • 计算机视觉与深度学习-经典网络解析-VGG-[北邮鲁鹏]

    目录标题 VGG 参考 VGG网络贡献 使用尺寸更小的 3 times 3 卷积串联来获得更大的感受野 放弃使用 11 times 11 和 5 times 5 这样的大尺寸卷积核 深度更深 非线性更强 网络的参数也更少 去掉了AlexNe
  • 黑马并发编程JUC总结

    并发编程总结1 并发编程 2 进程和线程 2 1定义 2 2并发和并行 2 3应用 异步调用 并发应用 3 java线程 3 1线程创建 创建线程方法1 创建方法2 Thread和Runable的区别 创建方法3 3 2线程运行 3 3线程
  • [Coursera 数字图像和视频处理基础]第一周

    开始跟Coursera上的数字图像和视频处理基础这门课 这次学习笔记记录下第一周的学习内容 第一周的内容很少 介绍了一些非常基础的知识 概括如下 并且记录了最后的答题作业 课程主页截图 1 模拟VS数字信号 首先是信号的定义 我搜了一点资料
  • 稠密连接网络(DenseNet)

    ResNet极大地改变了如何参数化深层网络中函数的观点 稠密连接网络 DenseNet Huang et al 2017 在某种程度上是ResNet的逻辑扩展 让我们先从数学上了解一下 1 从ResNet到DenseNet 回想一下任意函数
  • python: 字典 (dict) 的使用

    摘要 在刷 leecode 的题目时 会经常使用哈希表 在 python 中称为字典 dict 由于本人平时不怎么多使用字典 在真正运用时经常忘记其常规用法 特别是其成员函数的使用 因此 本人根据自己在刷 leecode 时经常使用字典的方
  • 深度学习与计算机视觉系列(8)_神经网络训练与注意点

    作者 寒小阳 时间 2016年1月 出处 http blog csdn net han xiaoyang article details 50521064 声明 版权所有 转载请联系作者并注明出处 1 训练 在前一节当中我们讨论了神经网络静
  • Memcache查看列出所有key方法

    Memcached查看列出所有key方法 测试的过程中 发现Memcached没有一个比较简单的方法可以直接象redis那样keys 列出所有的Session key 并根据key get对应的session内容 具体操作如下 登录MemC
  • bugkuctf-Simple_SSTI_2

    方法一 tplmap 直接扫 python2 tplmap py u http 114 67 175 224 10589 flag 可以注入 使用 os shell提权 python2 tplmap py u http 114 67 175
  • 7.recurrent_neural_network

    device torch device cuda if torch cuda is available else cpu sequence length 28 input size 28 hidden size 128 num layers
  • windows环境与Linux环境下调用c++程序

    在此之前 需要在java编译软件IDEL中配置本地的Maven仓库等 可自行网上搜索配置 一 在Windows系统下调用c 软件生成的dll文件 1 在IDEL中创建Maven工程 配置下载jna包等 在pom文件中写入如下配置即可
  • 软件测试2019:第三次作业

    一 单元测试的任务有哪些 1 模块接口测试 2 模块局部数据结构测试 3 模块边界条件测试 4 模块中所有独立执行通路测试 5 模块的各条错误处理通路测试 二 代码评审方法有哪些 你认为哪一种比较有效 理由是什么 代码评审方法分为代码走查和
  • 什么时候开始使用Redis

    思考这个问题的本质就是要学会取舍和选型 技术选型非常重要 大多人为了技术而技术 这是不可取的 就想小彬认为微服务必须解决分布式事务一样 但他却不知道为什么要用分布式事务 从而不知道什么时候要用分布式事务 就想Redis一样 什么时候要用Re
  • jmap 文件解析_干货分享丨jvm系列:dump文件深度分析

    摘要 java内存dump是jvm运行时内存的一份快照 利用它可以分析是否存在内存浪费 可以检查内存管理是否合理 当发生OOM的时候 可以找出问题的原因 那么dump文件的内容是什么样的呢 JVM dump java内存dump是jvm运行
  • 【springboot】如何在自己的springboot项目中引用别的springboot项目jar

    正好今天碰到了 就在这里总结下 习惯了将公用的项目打包成jar 然后当做工具类引入到自己项目中 直接调用 感觉甚是方便 但有没有发现 平时我们引用的大部分情况下是一个maven项目 然后打包好的jar也是maven项目的结构 所以我们可以正
  • VS使用技巧汇总

    总目录 文章目录 总目录 前言 一 快捷技巧 1 代码片段快捷方式 2 选择性粘贴 3 快速停靠窗口 4 多行同步快速编辑 5 引用命名空间 6 整行上下移动 7 快捷键 二 VS功能 1 打开VS自带反编译 2 VS扩展插件 三 其他 总
  • win10远程登录Ubuntu14.04图形化界面

    一 使用场景 因工作原因 需要在window与Linux系统同时操作 由于虚拟机卡顿 十分影响工作效率 于是找领导又申请一台电脑 Ubuntu主机主要日常代码编译与git操作 window主机主要用于日常沟通 资料查询 测试研发 windo
  • go语言重大bug,make缓存读取数据漏洞,4096漏洞

    做一个小程序 需要对文件内容分片读取 但是读取过程中发现数据读取不全 经测试多个make缓存读取文件时发现问题 以下为漏洞测试部分 一 生成测试文件 AAA txt 创建一个AAA txt文件 写入1万个A wFile os OpenFil
  • KMP算法原理

    所有下标从0开始 子串的定位操作通常称为串的模式匹配 它求的是子串 或称模式串 在主串中的位置 前缀 除最后一个字符外 字符串的所有头部子串 后缀 除第一个字符外 字符串的所有尾部子串 部分匹配值 字符串的前缀和后缀的最长相等前后缀长度 字