KMP算法

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: KMP算法

一、什么是BF算法

介绍KMP算法前,我们要先了解BF算法。

1、概念

       BF算法,即暴力(Brute Force)算法,是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种蛮力算法。——来自百度百科

        而BF算法就是一种字符串模式匹配的算法给出一个主串和一个子串,判断主串中有没有子串,有就返回子串在主串中的第一个下标。

       这种字符串匹配的匹配模式的算法很容易想到,但是时间复杂度很高,假设主串的长度为n,子串的长度为m,最坏情况下的时间复杂度:O(n*m),最好情况下的时间复杂度:O(n+m)

2、画图解析

定义i和j,同时遍历i和j,如果i和j下标的元素相同,就同时++,如图,i和j第一次停止遍历位置

这时,i位置的下标回退到i-j+1位置,但是j下标要到0位置,再次同时遍历,

依次类推,直到j能遍历完,就能找到子串在主串第一次出现的位置,i-j就是子串在主串中的首下标,如图:

3、代码展示

public int bf(String str, String sub) {
        int lenStr = str.length();//主串长度
        int lenSub = sub.length();//子串长度
        if(str == null || sub == null) {
            return -1;
        }
        if(lenStr == 0 || lenSub == 0) {
            return -1;
        }
        int i = 0;//遍历子串的
        int j = 0;//遍历主串的
        while (i < lenStr && j < lenSub) {
            if(str.charAt(i) == sub.charAt(j)) {
                i++;
                j++;
            } else{
                i = i - j + 1;
                j = 0;
            }
        }
        //j遍历完
        if(j >= lenSub) {
            return i - j;
        }
        //i没遍历完,j也没遍历完
        return -1;
    }

代码解析:

       定义i和j下标进行遍历,如果i<主串长度&&j<子串长度,就判断这两下标的元素是否相等,相等就同时++,不相等 i 就要回退到i-j-1下标位置,j就要回退到0位置,重新开始判断,依次类推,直到这个循环结束,判断j是否>=子串长度,如果大于等于就是遍历完j字符串了,我们返回i-j下标位置,没有遍历完就是主串没有这个子串,我们返回一个-1


二、什么是KMP算法

1、概念:

       KMP算法是三位学者在 Brute-Force算法(暴力求解算法)的基础上同时提出的模式匹配的改进算法。Brute- Force算法在模式串中有多个字符和主串中的若干个连续字符比较都相等,但最后一个字符比较不相等时,主串的比较位置需要回退。KMP算法在上述情况下,主串位置不需要回退,从而可以大大提高效率——来自百度百科。

       而KMP算法就是一种字符串模式匹配的算法给出一个主串和一个子串,判断主串中有没有子串,有就返回子串在主串中的第一个下标。

      KMP算法和BF算法的区别:KMP算法的i位置下标不会回退,而j位置下标不一定回退到0,而是回退到特殊位置,这个特殊位置我们会用next数组来记录

2、画图解析:

       只看概念是不是觉得比较抽象,我们来画图解析一下

如图:

我们先直接跳过前面的遍历,找到一个比较特殊的j下标,如图:

这种情况,我们知道KMP算法的i下标是不回退的,j下标回退的特定位置,这时,j下标是回退到2位置,如图:

这时我们看,j下标的前面和i下标的前两个元素是不是一样的?那么我们就不用像BF算法那样,把j下标回退到0位置,多遍历这两变。那我们再想想,这种情况是怎么回事呢?

      (看这里时对照着图会好理解很多)这是因为i和j能同时走到某个位置,那么前面肯定是有相同的元素的,但是前提还要子串的j下标位置前面有两个相同的字符串那么i遍历的同时,主串也肯定会遍历到两个相同的字符串在这基础上如果遍历到某个主串和子串的元素不相同时,那么这时i下标前面的后一个相同的字符串,可以和j下标前一个相同的字符串相匹配这时,i下标位置是不回退的,j下标就回退到前一个相同的字符串下标+1,而不用回退到0位置了,这也是为啥KMP算法的i下标是不回退的,但是j要回退到某个特定位置,而这个特定位置下标就是第一个相同的字符串末位置+1。      

由上面我们可以引出,子串的每一个下标都是有特定的回退位置的,这时我们就要求出这些特定位置,我们把子串回退的特定位置放在next[]数组下标中。

3、next数组

       (1)肉眼求next数组方式

求子串的next数组:在子串中找的0下标位置到j-1下标位置,判断是否有两个相同的0下标位置到j-1下标位置的字符串,如果有,子串的j下标位置在next数组中放的就是相同的字符串元素个数,没有的话这个下标j在next数组就放0。

我们先用肉眼来求两个字符串的next数组练练手:

子串:sub = "a,b,a,b,c,,a,b,c,d,,a,b,c,d,e",求next数组

next[0] = -1, next[1] = 0,除了next数组的0下标和1小标比较特殊外,其他都按照上面KMP算法的画图解析步骤来求得,红色字即为下标

所以next[] = {-1,0,0,1,2,0,1,2,0,0,1,2,00}。

子串:sub = "a,b,c,a,b,c,a,b,c,a,b,c,d,a,b,c,d,e",求next数组

所以next[] =  {-1,0,0,0,1,2,3,4,5,6,7,8,9,0,1,2,3,0}。

      (2)如何求next数组?

我们求出一个子串中的next数组,如图

假设next[j] = k

那么我们就能写出如下的公式,下面进行公式推导

我们再判断i和k下标位置元素是否相同

i和k下标位置元素相同情况:

推导:

我们是用 i 遍历数组的,这个推导也就是判断 i+1 位置在next数组中的值。

所以,这时:next[i + 1] = k + 1

i和k下标位置元素不相同情况:

我们想想next[i+1] = ?

这时,k就要回退到next[k]位置判断回退完后的值和i下标的值是否相同

如图:

k下标的值和i下标的值相同,那么这种情况就又回到了上面p[i] = p[k]时的情况,处理方法和上面一样:next[i+1] =  k+1

那如果k回退到-1位置呢,处理方法也是next[i+1] = k+1,也是i+1下标的next数组为0

       (3)next数组的代码展示

注意:我们在代码展示,遍历i的过程中,是不知道当前i下标在next数组中的位置的,但是我们知道前一个下标在next数组中的位置,所以在代码中,我们判断的是next[i-1]和k下标位置的值是否相同,求i下标在next数组中的值。

public static void getNext(String sub, int[] next) {
        next[0] = -1;
        next[1] = 0;
        int i = 2;//提前走了一步
        int k = 0;//前一项的k
        //i下标从2开始
        for(;i <= sub.length(); i++) {
            //p[i-1] = p[k]
            if (k == -1 || sub.charAt(i - 1) == next[k]) {
                next[i] = k + 1;
                k++;
            } else {
                //回退
                k = next[k];
            }
        }
    }

三、KMP算法的实现(Java实现)

KMP算法的实现思路和BF思想差不多,只不过子串的回退位置不一样,遍历主串的下标不回退

代码展示

public static int kmp(String str, String sub, int pos) {
        if(str == null || sub == null) {
            return -1;
        }
        //pos:从pos开始遍历
        int lenStr = str.length();
        int lenSub = sub.length();
        if (lenStr == 0 || lenSub == 0) {
            return -1;
        }
        if(pos < 0 || pos >= lenStr) {
            return -1;
        }
        int i = pos;//遍历str的下标
        int j = 0;//遍历sub的下标
        int[] next = new int[lenSub];
        getNext(sub, next);
        while(i < lenStr && j < lenSub) {
            if(j == -1 || str.charAt(i) == sub.charAt((j))) {
                i++;
                j++;
            } else {
                //i不回退,j回退到next[j]位置
                j = next[j];
            }
        }
        if(j >= lenSub) {
            return i - j;
        }
        return -1;
    }

和next数组结合的代码呈现情况:

public static void getNext(String sub, int[] next) {
        next[0] = -1;
        next[1] = 0;
        int i = 2;//提前走了一步
        int k = 0;//前一项的k
        //i下标从2开始
        for(;i <= sub.length(); i++) {
            //p[i-1] = p[k]
            if (k == -1 || sub.charAt(i - 1) == next[k]) {
                next[i] = k + 1;
                k++;
            } else {
                //回退
                k = next[k];
            }
        }
    }
    public static int kmp(String str, String sub, int pos) {
        if(str == null || sub == null) {
            return -1;
        }
        //pos:从pos开始遍历
        int lenStr = str.length();
        int lenSub = sub.length();
        if (lenStr == 0 || lenSub == 0) {
            return -1;
        }
        if(pos < 0 || pos >= lenStr) {
            return -1;
        }
        int i = pos;//遍历str的下标
        int j = 0;//遍历sub的下标
        int[] next = new int[lenSub];
        getNext(sub, next);
        while(i < lenStr && j < lenSub) {
            if(j == -1 || str.charAt(i) == sub.charAt((j))) {
                i++;
                j++;
            } else {
                //i不回退,j回退到next[j]位置
                j = next[j];
            }
        }
        if(j >= lenSub) {
            return i - j;
        }
        return -1;
    }

都看到这了,点个赞再走呗,谢谢谢谢谢!!!

相关文章
|
6月前
|
算法
数据结构中的KMP算法及其改进算法
KMP算法通过引入部分匹配表,有效避免了重复计算,从而将字符串匹配的时间复杂度降低到O(m+n)。通过进一步优化next数组,KMP算法的效率得到了进一步提升。对于大规模字符串匹配问题,KMP算法及其改进算法提供了高效的解决方案,是计算机科学领域的经典算法之一。
102 3
|
2月前
|
算法
第四章 KMP算法理论基础
第四章 KMP算法理论基础
22 0
|
2月前
|
算法
KMP算法
KMP算法
37 0
|
4月前
|
算法 C++
A : DS串应用–KMP算法
这篇文章提供了KMP算法的C++实现,包括计算模式串的next数组和在主串中查找模式串位置的函数,用于演示KMP算法的基本应用。
|
5月前
|
数据采集 算法 JavaScript
揭开JavaScript字符串搜索的秘密:indexOf、includes与KMP算法
JavaScript字符串搜索涵盖`indexOf`、`includes`及KMP算法。`indexOf`返回子字符串位置,`includes`检查是否包含子字符串。KMP是高效的搜索算法,尤其适合长模式匹配。示例展示了如何在数据采集(如网页爬虫)中使用这些方法,结合代理IP进行安全搜索。代码示例中,搜索百度新闻结果并检测是否含有特定字符串。学习这些技术能提升编程效率和性能。
140 1
揭开JavaScript字符串搜索的秘密:indexOf、includes与KMP算法
|
4月前
|
算法
KMP算法
KMP算法
36 0
|
5月前
|
算法 Java
KMP算法详解及其在字符串匹配中的应用
KMP算法详解及其在字符串匹配中的应用
|
6月前
|
人工智能 算法 BI
一篇文章讲明白KMP算法(俗称看毛片算法)
一篇文章讲明白KMP算法(俗称看毛片算法)
64 0
|
6月前
|
算法 Java
Java数据结构与算法:字符串匹配算法之KMP算法
Java数据结构与算法:字符串匹配算法之KMP算法
|
7月前
|
算法
下一篇
DataWorks