Java笔记——KMP算法

news/2024/10/18 18:28:00/

KMP算法

文章目录

  • KMP算法
    • KMP算法介绍
    • 主要逻辑
    • Next数组
    • KMP搜索
    • 代码解释
      • 生成next数组
      • 模式串匹配
    • 源码展示

KMP算法介绍

KMP算法是一种串的模式匹配算法,用来求子串在主串的位置。是数据结构中比较难的一种算法。KMP算法的核心在于点在于如何利用子串生成next数组,和如何利用next数组来寻找子串在主串的位置

主要逻辑

在模式串匹配的过程中,一般的思路是利用两个指针,分别指向主串和模式串(子串),然后从头比较,如果两个指针指向的字符是一样的,就将两个指针同时后移,接着比较后面的字符。当两个指针指向的字符不再一样的时候,就要将指向主串元素的指针回溯到一定位置,同时指向模式串元素的指针也回溯到一定位置,然后重新比较。

当指向模式串元素的指针遍历完模式串的同时,恰好遍历完主串或者没有遍历完主串,则说明已经找到模式串在主串中的位置
匹配成功

当指向模式串元素的指针没有遍历完模式串的时候已经把主串遍历完了,则说明模式串在主串中不存在匹配的位置

匹配失败

这种思路最主要的地方在于如何让模式串右移的过程做到高效,此时就有了两种思路,一种是简单的BF算法(暴力算法)一种就是KMP算法

BF算法主要是最小程度的将模式串右移,从而达到不遗漏任何一个可能的目的。在模式串匹配的工程中,当主串上的一个字符和模式串不匹配的时候,就将指向主串元素的指针回溯到主串起始比较的下一个元素,指向模式串元素的指针回溯到模式串最开始元素,然后继续开始比较。这种思路主要每次比较失败都将模式串右移一个字符的长度,时间复杂度是O(n*m)

KMP算法相比于BF算法主要的特点是利用next数组提高模式串比较的效率。在模式串比较的过程中,根据已经生成next数组将将模式串最大限度的右移。在将模式串右移的过程中,还做到了指向主串元素的指针不回溯,按照next数组将指向模式串数组的指针进行回溯。时间复杂度为O(n+m)
KMP

Next数组

当我们在进行字符串匹配的BF算法过程中,因为在一遍一遍的遍历模式串才会导致时间复杂度那么高。当我们如果可以遍历完一遍模式串以后将模式串的规律存储到一个数组中,然后根据这个数组中存储的数据进行回溯的话,就会大大降低时间复杂度,这就是为什么要生成next数组的原因。

一个人能能走的多远不在于他在顺境时能走的多快,而在于他在逆境时多久能找到曾经的自己。 ——KMP

Next数组是KMP算法的核心,Next数组决定了如何将模式串做到最大程度的右移,在next数组中存储的数据代表着如果当前字符模式串匹配失败,指向模式串元素的指针要回溯的位置。这个回溯的位置一般是看模式串中相同的字符串前缀和字符串后缀,当一个字符串前缀和后缀一致的时候,就会进行按照字符串前缀和字符串后缀的长度进行生成next数组的数值。

在一个字符串中,它有字符串前缀和字符串后缀,前缀是指最后一个字符以外,一个字符串的全部头部的组合,后缀是指第一个字符以外,一个字符串的全部尾部组合。就以字符串"ababc",它的字符串前缀有a,ab,aba,abab,它的字符串后缀有c,bc,abc,babc,把每一个字符串前缀都当成一个独立的字符串,寻找每个字符串的公共前后缀。‘a’,‘ab’没有公共前后缀,"aba"存在公共前后缀,是’a’,所以生成的模式匹配值是1,“abab"存在公共前后串,是"ab”,所以生成的模式匹配值是2。最后字符串"ababac"生成的next数组是00120,生成好Next数组就可以根据这个数组进行KMP模式串匹配

KMP搜索

KMP搜索核心点之一就是如何利用next数组,已知next数组是根据模式串的规律进行生成的,如何利用这个规律将模式串匹配做到更快更高效就是KMP算法的一个关键。

根据Next数组的介绍可以得知,next的数值代表着最长模式串前缀子串的公共前后缀的最长长度,同时也代表着要回溯的位置。当模式串和主串的元素不再一致的时候,就要根据next数据将指向模式串字符的指针进行回溯,回溯的位置就是next数组的值。当next数组此时的值为0的时候,就要根据情况进行一遍检查。

代码解释

生成next数组

    //获取到一个字符串的部分匹配值public static int[] KMP_next(String dest) {//创建一个数组next,保存部分匹配值。长度跟模式串长度一致int[] next = new int[dest.length()];next[0] = 0;//如果字符串是长度为1 部分匹配值就是0//当字符串的长度为1的时候,没有前缀也没有后缀,更不可能有公共前后缀,所以默认是0//i从第二位开始,j从第一位开始,遍历整个模式串for (int i = 1, j = 0; i < dest.length(); i++) {//当dest.charAt(j) != dest.charAt(i),我们需要从next[j-1]获取新的j//知道我们发现有dest.charAt(j) == dest.charAt(i)成立才停止while (j > 0 && dest.charAt(j) != dest.charAt(i)) {//这里设置j>0是为了防止陷入死循环中(i指向的元素不等于第一个元素,并且j)j = next[j - 1];//当est.charAt(j) != dest.charAt(i),进行一个回溯,一直到dest.charAt(j) == dest.charAt(i)或者j已经指向第一个元素的位置}//当dest.charAt(j) == dest.charAt(i)满足时,部分匹配值就是+1if (dest.charAt(j) == dest.charAt(i)) {j++;//这里进行j++,刚好让j进一位,但是还是在i后面}next[i] = j;//将j的位置放进next数组中}return next;}

模式串匹配

	//KMP搜索算法public static int KmpSearch(String str1, String str2) {//调用KMP_next方法生成一个next数组int[] next = KMP_next(str2);//遍历主串for (int i = 0, j = 0; i < str1.length(); i++) {//这里也是一个回溯,但是这里的回溯是根据next数组,将指向模式串元素的指针回溯到指定位置,从而实现模式串的右移。if (j > 0 && str1.charAt(i) != str2.charAt(j)){j = next[j - 1];}//如果模式串和主串的被指向的元素相同就将j后移,循环结束后i会自动后移一位,所以这里就不用写i++if (str1.charAt(i) == str2.charAt(j)) {j++;}//判断j是不是已经指向模式串最后一个字符,如果指向了模式串最后一个字符,就说明已经在主串中找到跟模式串相同的子串,此时直接根据i,j就可以确定子串在主串中位置if (j == str2.length()) {return i - j + 1;}}//当主串遍历完成但是没有提前return的时候,说明没有模式串匹配失败,没有在主串中找到和模式串相同的子串,此时就要返回一个主串中不存在的位置用来说明没有找到,一般用-1来说明return -1;}

for循环里面的三个if判断不能调换顺序,判断j是否指向模式串最后一个元素的位置的if判断必须放到后面,因为放到开头时会发生i已经自增一次的情况,return语句也要相应的发生变化。

源码展示

import java.util.Arrays;public class KMP {public static void main(String[] args) {String str1 = "BBC ABCDAB ABCDABCDABDE";String str2 = "ABCDABA";int[] next = KMP_next(str2);System.out.println("next=" + Arrays.toString(next));int index=KmpSearch(str1,str2);System.out.println(index);}//KMP搜索算法public static int KmpSearch(String str1, String str2) {int[] next = KMP_next(str2);//遍历for (int i = 0, j = 0; i < str1.length(); i++) {if (j>0&&str1.charAt(i)!=str2.charAt(j)){j=next[j-1];}if (str1.charAt(i) == str2.charAt(j)) {j++;}if (j == str2.length()) {return i - j + 1;}}return -1;}//获取到一个字符串的部分匹配值public static int[] KMP_next(String dest) {//创建一个数组next,保存部分匹配值int[] next = new int[dest.length()];next[0] = 0;//如果字符串是长度为1 部分匹配值就是0for (int i = 1, j = 0; i < dest.length(); i++) {//当dest.charAt(j) != dest.charAt(i),我们需要从next[j-1]获取新的j//知道我们发现有dest.charAt(j) == dest.charAt(i)成立才停止while (j > 0 && dest.charAt(j) != dest.charAt(i)) {j = next[j - 1];}//当dest.charAt(j) == dest.charAt(i)满足时,部分匹配值就是+1if (dest.charAt(j) == dest.charAt(i)) {j++;}next[i] = j;}return next;}
}

http://www.ppmy.cn/news/86510.html

相关文章

SpringBoot --- 原理篇

一、自动配置 1.1、bean加载方式 1.1.1、方式一&#xff1a;配置文件<bean/>标签 最初级的bean的加载方式其实可以直击spring管控bean的核心思想&#xff0c;就是提供类名&#xff0c;然后spring就可以管理了。所以第一种方式就是给出bean的类名&#xff0c;通过反射机…

面试题:如何测试登录功能

最近在做一个创新项目&#xff0c;这个项目有二个平台&#xff0c;每个平台都有前后端&#xff0c;故有四个系统&#xff0c;每个系统都有登录功能&#xff0c;而且不同系统代码设计方式都有所差异&#xff0c;所以就这个登录功能而言就要测试四次&#xff0c;看似一个简单的登…

MaxHub智能电视使用123

开机和关机 电视正下方&#xff0c;中央有一个圆形按钮。开机时&#xff0c;轻按此按钮1下&#xff0c;智能电视开始启动。启动后会显示MAXHUB。 如果使用中想让智能电视进入休眠状态&#xff0c;轻按此按钮1下即可。此时按钮变为红色。唤醒时&#xff0c;轻按此按钮1下&#…

git reset 之后,再找回失去的代码

有时候&#xff0c;我们会遇到这种情况 获取提交记录 git log --prettyone比如&#xff0c;git 提交记录如下 1f0e8646ee5c5e7bfffe0ce38fe183300c926065 (HEAD -> main, origin/main) five dcd605c388481e68d396d6c27949e9de4a11ab77 fourth ef98780ec960f95c2ab1a9281f02…

Linux 内核参数:panic 相关

源码基于:Linux 5.4 针对节点: /proc/sys/kernel/panic/proc/sys/kernel/panic_print/proc/sys/kernel/panic_on_oops/proc/sys/kernel/panic_on_warn/proc/sys/kernel/panic_on_rcu_stall相关博文: Linux 内核参数:panic_on_oom Linux内核oops panic简析 0. 官方描述 /…

C++的最后一道坎 | 百万年薪的程序员

| 导语 C 的起源可以追溯到 40 年前&#xff0c;但它仍然是当今使用最广泛的编程语言之一&#xff0c;C发明人Bjarne Stroustrup 一开始没想到 C 会获得如此大的成功&#xff0c;他说&#xff1a;“C 的成功显然令人惊讶。我认为它的成功取决于其最初的设计目标&#xff0c;就是…

Spring Cloud面试题

组件 Spring Cloud Eureka&#xff1a;服务注册与发现 Spring Cloud Zuul&#xff1a;服务网关 Spring Cloud Ribbon&#xff1a;客户端负载均衡 Spring Cloud Feign&#xff1a;声明性的Web服务客户端 Spring Cloud Hystrix&#xff1a;断路器 Spring Cloud Config&#xff1…

统计一个数的二进制中1的个数(三种方法)

那么好了好了&#xff0c;宝子们&#xff0c;今天给大家分享一篇经典例题的三种实现方法&#xff0c;来吧&#xff0c;开始整活&#xff01;⛳️ 一、基础法 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int number_of_one(int n) {int count 0;while(n){if…