KMP算法详解

1、什么是kmp算法?

kmp算法是一种字符串匹配算法,可以用于在一篇文章中查找关键字所在的位置,如果存在返回第一次出现关键位置的索引,如果不存在返回-1,kmp算法可以通过俗称看毛片算法记忆(拼音首字母组合)。

2、kmp算法的思想是什么?

kmp算法的主要思想是:在文本串(文章)中查找模式串(关键字)时,利用模式串和文本串中已经匹配的部分,跳过一些无用的比较,使文本串的标记指针不会回溯,模式串的标记指针移动。其实主要是模式串中如果有部分内容和文本串中一致,我们需要研究这些已知的匹配内容,这相当于我们需要研究模式串,发现模式串中存在的规律。
例如: 文本串:abcaabce
模式串:abce
上面的文本串和模式串第一次比较时,由于索引为3的位置a和e不相同,假设文本串的索引i=3,模式串的索引j=3,kmp的思想是由于前面的abc已经匹配完成,我们从中可以知道里面没有a,所以此时我们的文本串中标记指针不变依然是i=3,但是模式串的指针变化j=0,这样可以省略一些无用的比较,所以我们需要研究模式串发现其中的规律。
模式串中的规律主要有以下几个方面,假设模式串如图:
在这里插入图片描述
1. 为什么可以跳过模式串的一些不必要的比较:
当P[j]!=T[i];
已知P[0 ~ j-1]=T[i-j ~ i-1];
则:P[j-k ~ j-1] = T[i-k ~ i-1] ;
当存在:p[0 ~ k-1] =p[j-k ~ j-1];
则:P[0 ~k-1] = T[i-k ~ i-1] ;
所以可以跳过模式串的前面k-1个数值的比较,节省时间。

2. 图中的k是如何得到的?
注意上面的公式要求:p[0 ~ k-1] =p[j-k ~ j-1],这里就存在两个概念前缀和后缀,前缀指的是除了最后一个元素,前面元素的顺序组合,同理理解后缀,比如abab的前缀是{a, ab,aba},而abab的后缀是{b,ab,bab},其前缀中和后缀中相同部分的最大值是2,如果存在索引则:p[0 ~ 1] = p[2 ~ 3];所以,如果我们想跳过一些无用的比较,我们首先需要确定的是模式串中不同索引位置的k值,通过上面图可知如果跳过索引则需要比较的是文本串索引为7的b和模式串中索引为2的c,所以当p[6]!=T[7],文本串的i不变,j移动到索引为2的位置,k=2,这是abcdab模式串前缀和后缀的最大值,求模式串中某索引的k值是除当前索引元素的前面元素前缀和后缀的最大值,我们使用next数组进行存放。
3. next[0]为什么是-1,不是0?
next[0]是指当模式串为abc,文本串为efg,当a!=e,则模式串的索引调到索引为next[0]处,如果next[0]会造成循环,所以next[0]!=0,另外,next[1]=0;因为索引为1的元素其前面只有一个元素,所以k=0;
4. 推导next[j]=k,当p[j]=p[k],next[j+1]=k+1;
next[j]=k,则p[0 ~ k-1] =p[j-k ~ j-1];,由于p[j]=p[k],则p[0 ~ k] =p[j ~ j-1], 所以next[j+1]=k+1=next[j]+1
5. 推导next[j]=k,当p[j]!=p[k],则k=next[k];(重点)
next[j]=k,则p[0 ~ k-1] =p[j-k ~ j-1],由于p[j]!=p[k],所以我们需要求p[0 ~ j]中前缀和后缀相同的最大值,假设存在最大值是b,则p[0 ~b -1] =p[j-b+1 ~ j],则next[j + 1]= b,如果b大于k,这可以保证p[j]=p[k],但是p[j]!=p[k],所以b要小于k,则b在0 ~ k中间的数,所以在p[0 ~ k-1]中存在p[j]的前缀和后缀的最大值,将0 ~ k-1分为两部分,k-1和0 ~ k ~ 2,k-1和索引j比较,0 ~ k ~ 2和j-k+1 ~ j-1,由于p[0 ~ k-1] =p[j-k ~ j-1], j-k+1 ~ j-1等于1 ~k -1 ,则求解的是next[k] ,则k=next[k];如果p[j]=p[k],则k++,否则k=next[next[k]]

3、kmp算法的代码实现?

 public static void setNext(char[] target, int[] arr) {
        arr[0] = -1;
        int k = -1;
        int i = 0;
        while (i < target.length - 1) {
            if (k == -1 || target[i] == target[k]) {
                ++k;
                ++i;
                arr[i] = k;
            } else {
                k = arr[k];
            }
        }
    }

    public static int getIndex(char[] text, char[] target) {
        int[] arr = new int[target.length];
        setNext(target, arr);
        for (int i : arr) {
            System.out.println(i);
        }
        int l = 0;
        int k = 0;
        while (l < text.length) {
            if (k == -1 || text[l] == target[k]) {
                ++l;
                ++k;
            } else {
                k = arr[k];
            }
            if (k == target.length) {
                return l - k;
            }
        }
        return -1;
    }

4、kmp算法的优化

kmp算法的优化:kmp算法中存在的问题是:当T[i]!=p[j],但是p[next[j]]=p[j],由于T[i]!=p[j],所以p[next[j]]!=T[i],有重复判断,所以当p[j]=p[next[j]]需要改变,使next[j]=next[next[j]];代码如下:

 private static void getNext(String target, int[] next) {
        char[] chars = target.toCharArray();
        next[0]=-1;
        int k = -1;
        int j = 0;
        while (j < chars.length-1) {
            // 比较前缀和后缀或者k=-1
            if (k == -1 || chars[j]==chars[k] ) {
                // 如果前面成立则next[j]=k;则下面比较的是chars[j] == chars[next[j]]
                if (chars[++j] == chars[++k]) {
                    // k=next[j] 下面为next[j]=next[k]=next[next[j]]
                    next[j]=next[k];
                } else {
                    next[j]=k;
                }
            } else {
                k = next[k];
            }
        }
    }

5、kmp算法的总结

kmp算法用于快速的字符串匹配,先从模式串中获取足够的规律,再快速再文本串中匹配,文本串中的索引不回溯,kmp算法的重点是获取next数组,如果要获取next数组,需要明白模式串中前缀和后缀的规律(p[0 ~ k-1] =p[j-k ~ j-1])。假如文本串的长度为m,模式串的长度为n,则获取next数组花费时间为O(n),文本串中比较的时间为O(m),总的时间为O(n+m)

6、kmp算法类比

kmp算法类思想似于:如果我们想快速的提升自己某个方面的知识,我们首先必须自己和自己比较,了解自己的相关知识体系,然后才能快速的提升,是一种先了解自己,才可以快速的了解他人的思想

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值