KMP算法之java实现

一、KMP算法

KMP算法的理解请参考上一篇博客:http://blog.csdn.net/roy_70/article/details/78321930
这里说一下具体如何来用代码实现KMP算法

二、部分匹配表

接下来说一下部分匹配表是如何生成的。首先,要了解两个概念:”前缀”和”后缀”。 “前缀”指除了最后一个字符以外,一个字符串的全部头部组合;”后缀”指除了第一个字符以外,一个字符串的全部尾部组合。
例如:单词level的前缀有{l,le,lev,leve}四个,后缀有{evel,vel,el,l}四个,然后前缀和后缀集合取交集,得到{l},只有一个元素,长度为1,因此level的部分匹配值就是1。

搜索词QWERQWR
部分匹配值0000120

再回来看上面的字符串例子:
“Q”的前缀和后缀都为空集,共有元素的长度为0;
“QW”的前缀为[Q],后缀为[W],共有元素的长度为0;
“QWE”的前缀为[Q, QW],后缀为[WE, E],共有元素的长度0;
“QWER”的前缀为[Q, QW, QWE],后缀为[WER, ER, R],共有元素的长度为0;
“QWERQ”的前缀为[Q, QW, QWE, QWER],后缀为[WERQ, ERQ, RQ, Q],共有元素为”Q”,长度为1;
“QWERQW”的前缀为[Q, QW, QWE, QWER, QWERQ],后缀为[WERQW, ERQW, RQW, QW, W],共有元素为”QW”,长度为2;
“QWERQWR”的前缀为[Q, QW, QWE, QWER, QWERQ, QWERQW],后缀为[WERQWR, ERQWR, RQWR, QWR, WR, R],共有元素的长度为0。

三、next数组

由上文,我们已经知道,字符串“QWERQWR”各个前缀后缀的最大公共元素长度分别为:

搜索词QWERQWR
部分匹配值0000120

失配时,模式串向右移动的位数为:已匹配字符数 - 失配字符的上一位字符所对应的最大长度值
上文利用这个表和结论进行匹配时,我们发现,当匹配到一个字符失配时,其实没必要考虑当前失配的字符,更何况我们每次失配时,都是看的失配字符的上一位字符对应的最大长度值。如此,便引出了next 数组。
给定字符串“ABCDABD”,可求得它的next 数组如下:

搜索词QWERQWR
部分匹配值0000120
next数组-1000012

把next 数组跟之前求得的最大长度表对比后,不难发现,next 数组相当于“最大长度值” 整体向右移动一位,然后初始值赋为-1。意识到了这一点,你会惊呼原来next 数组的求解竟然如此简单:就是找最大对称长度的前缀后缀,然后整体右移一位,初值赋为-1(当然,你也可以直接计算某个字符对应的next值,就是看这个字符之前的字符串中有多大长度的相同前缀后缀)。
所以我们求得next数组的方法代码为:

public int[] getNext(char[] p) {
        // 已知next[j] = k,利用递归的思想求出next[j+1]的值
        // 如果已知next[j] = k,如何求出next[j+1]呢?具体算法如下:
        // 1. 如果p[j] = p[k], 则next[j+1] = next[k] + 1;
        // 2. 如果p[j] != p[k], 则令k=next[k],如果此时p[j]==p[k],则next[j+1]=k+1,
        // 如果不相等,则继续递归前缀索引,令 k=next[k],继续判断,直至k=-1(即k=next[0])或者p[j]=p[k]为止
        int pLen = p.length;
        int[] next = new int[pLen];
        int k = -1;
        int j = 0;
        next[0] = -1; // next数组中第一位next[0]为-1
        while (j < pLen - 1) {
            if (k == -1 || p[j] == p[k]) {
                k++;
                j++;
                next[j] = k;
            } else {
                k = next[k];
            }
        }
        return next;
    }

三、KMP算法代码

public class KMP {
    public int indexOf(String source, String pattern) {
        int i = 0, j = 0;
        char[] src = source.toCharArray();
        char[] ptn = pattern.toCharArray();
        int sLen = src.length;
        int pLen = ptn.length;
        int[] next = getNext(ptn);
        while (i < sLen && j < pLen) {
            // 如果j = -1,或者当前字符匹配成功(src[i] = ptn[j]),都让i++,j++
            if (j == -1 || src[i] == ptn[j]) {
                i++;
                j++;
            } else {
                // 如果j!=-1且当前字符匹配失败,则令i不变,j=next[j],即让pattern模式串右移j-next[j]个单位
                j = next[j];
            }
        }
        if (j == pLen)
            return i - j;
        return -1;
    }
    public int[] getNext(char[] p) {
        // 已知next[j] = k,利用递归的思想求出next[j+1]的值
        // 如果已知next[j] = k,如何求出next[j+1]呢?具体算法如下:
        // 1. 如果p[j] = p[k], 则next[j+1] = next[k] + 1;
        // 2. 如果p[j] != p[k], 则令k=next[k],如果此时p[j]==p[k],则next[j+1]=k+1,
        // 如果不相等,则继续递归前缀索引,令 k=next[k],继续判断,直至k=-1(即k=next[0])或者p[j]=p[k]为止
        int pLen = p.length;
        int[] next = new int[pLen];
        int k = -1;
        int j = 0;
        next[0] = -1; // next数组中next[0]为-1
        while (j < pLen - 1) {
            if (k == -1 || p[j] == p[k]) {
                k++;
                j++;
                next[j] = k;
            } else {
                k = next[k];
            }
        }
        return next;
    }
}

测试代码:

public static void main(String[] args){
        KMP kmp = new KMP();
        String a = "QWERQWR";
        String b = "WWE QWERQW QWERQWERQWRT";
        int[] next = kmp.getNext(a.toCharArray());
        for(int i = 0; i < next.length; i++){
            System.out.println(a.charAt(i)+"    "+next[i]);
        }
        int res = kmp.indexOf(b, a);
        System.out.println(res);
    }

执行结果:

Q    -1
W    0
E    0
R    0
Q    0
W    1
R    2
15
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值