KMP算法的图示理解和代码实现

KMP算法的图示理解和代码实现

参考资料:左神算法课程。对其中KMP算法的学习理解。

解决的问题

字符串包含问题,在str1中是否存在str2这样的子串,如果存在返回子串开始的位置。注意:子串和子序列的区别。

理解KMP算法的前置知识:部分匹配表,即next数组。

next数组的值:表示在某个位置i,对于i前面的字符串,这个字符串的前缀集合和后缀集合中,前缀和后缀相等时候的最大长度。

例如:对于abcabcd这个字符串,如果当前在d这个位置上,其前面的子串就是abcabc,前缀集合是{a,ab,abc,abca,abcab},后缀集合是{c,bc,abc,cabc,bcabc},最长的公共前缀和后缀为abc,那么这个位置的next数组的值就是3。特别地,对于第一个字符,它前面没有字符串了,此时将next[0]的值表示为-1。

KMP算法的图示:

解释:

  • 当前的位置情况是,str1i位置字符和str20位置字符匹配,然后继续往后匹配,直到str1i1位置和str2i2位置,XY不相等,无法匹配。
  • 此时必须将str2向后推,那么推到哪里呢?笨办法是将str2往后推动一格,看str1i+1位置字符和str2的0位置字符是否匹配,但是根据next数组(部分匹配表)得知,T2T3是相等的,而且str1ii1位置之间的字符串和str20i2位置之间的字符串是匹配的,所以T1等于T3,故T1=T2=T3
  • 根据这个性质,只需要将str2的0位置和str1j位置对齐即可,j位置表示T1范围中的第一个位置。因为T1T2是相等的,即已经匹配好了,接下来str2只要从T2的下一个位置开始匹配即可,所以i2发生了更新,位于T2范围的下一个位置。

关于next数组的求解示意图:

解释:

  • 当前位于i位置,求解next[i],需要首先看前一个位置i-1,求解思想是利用之前的信息来求解当前位置的next值。
  • T1T2i-1位置上的最长公共前缀和后缀,pre位置是T1的后一个位置,那么首先比较pre位置的字符和i-1位置的字符是否相等,如果刚好相等,那么next[i] = next[i-1]+1,此时i位置上的最长前缀就是T1加上pre上的字符。
  • 如果pre位置的字符和i-1位置的字符不相等,那么把pre位置看做中间位置,求这个位置上的最长公共前缀和后缀,即T3T4,然后重复上面的步骤,知道pre不能再往前为止。

注意:需要明白pre位置的含义,将pre位置的元素和i-1位置的元素进行比较,如果相等则结束,如果不等,那么pre继续往前跳,直到不能跳为止,此时next[i] = 0

KMP算法代码实现

import java.util.Arrays;
public class KMP {
    /**
     * 利用kmp算法在str1中匹配str2
     * @param str1 待匹配的目标字符串
     * @param str2 模式串
     * @return 如果能在str1中匹配到str2,则返回str1中匹配上的字符串的开始位置;如果匹配不到,返回-1
     */
    public static int getIndexOf(String str1, String str2) {
        // 特殊情况
        if (str1 == null || str1 == null || str2.length() < 1 || str1.length() < str2.length()) {
            return -1;
        }
        int[] next = generateNextArray(str2);
        char[] strArr1 = str1.toCharArray();
        char[] strArr2 = str2.toCharArray();
        int i1 = 0, i2 = 0;
        while (i1 < strArr1.length && i2 < strArr2.length) {
            if (strArr1[i1] == strArr2[i2]) {
                i1++;
                i2++;
            } else if (next[i2] == -1) { // 此时i2在str2中的第一个位置,并且未匹配
                i1++;
            } else {
                i2 = next[i2];
            }
        }
        return i2 == strArr2.length ? i1-i2 : -1;
    }

    /**
     * 生成字符串的next数组
     * next数组中的值:表示当前位置i前面的字符串,这个字符串的前缀和后缀相等的最大长度
     * 举例:"ababac"和"ababcababak"将这两个例子带入下面的代码跑一遍就明白pre的含义和代码的逻辑。
     * @param str
     * @return
     */
    private static int[] generateNextArray(String str) {
        if (str.length() == 1) {
            return new int[]{-1};
        }
        char[] strArray = str.toCharArray();
        int[] next = new int[strArray.length];
        next[0] = -1; // 第一个位置前面没有字符串了,约定该位置的next值为-1
        next[1] = 0;
        int i = 2; // 从第三个字符开始往后考虑
        int pre = 0; // pre的含义:假设当前位置为i,pre表示i-1位置上的next值
        while (i < strArray.length) {
            // 此时pre表示i-1位置上的next值,那么比较i-1位置的字符和pre位置的字符,看是否相等
            if (strArray[i-1] == strArray[pre]) {
                next[i++] = ++pre;
            } else if (pre > 0) { // 如果匹配不上,pre就往前跳
                pre = next[pre];
            } else { // 如果没法往前跳了,那么i位置的next值就是0
                next[i++] = 0;
            }
        }
        return next;
    }
}

KMP算法的应用

1、在一个字符串的最后添加任意个字符,使得新的字符串包含两个原始串,比如abcabc,在末尾添加上abc之后得到新的字符串abcabcabc,这个新的字符串包含两个原始串abcabc

2、是否存在子树,即A中是否包含像B这样的子树。注意:子树和子结构的区别。

解决方案:将A和B两棵二叉树序列化为字符串之后,就将树的问题转化为字符串的问题,也就是将子树的问题转化为KMP的问题。序列化的方式可以是前序、中序、后序、层序,若选择前序遍历,则树A序列化之后的字符串为1,1,1,null,null,1,null,null,1,1,null,null,null,树B序列化之后的字符串为1,1,null,null,null,其中用,表示节点间的分隔符,用null来表示空节点,当然用其它的符号,比如#或者$都是可以的。序列化问题参考题目:剑指offer37

3、判断某个字符串str是否是str' x n这样的形式,比如abcabcabc就是这样的形式。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值