KMP算法

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

KMP算法常用于解决字符串匹配问题,如字符串B在字符串A中是否出现过?出现过几次?出现的起始下标?


提示:以下是本篇文章正文内容,下面案例可供参考

一、字符串匹配的暴力算法

问题:字符串s为"aaaaa", t为"aaaabaaaaac",求s串在t串中出现的起始下标,若没出现则返回-1。
我们首先相当的肯定是暴力解法,假设t串匹配到i,s串匹配到j,则有:
(1)如果当前字符匹配成功(即t[i] == s[j]),则i++,j++,继续匹配下一个字符。
(2)如果当前字符匹配失败(即t[i] != s[j]),则令i = i -(j - 1),j = 0。相当于每次匹配失败i回溯到当前起点的下一个位置,j直接置为0。
(3)显然,用暴力算法会有大量的回溯,每次只移动一位,浪费了大量的时间。
代码如下(示例):

public int violence(String s, String t){
        char[] chars1 = s.toCharArray();
        char[] chars2 = t.toCharArray();
        int i = 0;
        int j = 0;
        while (j < chars1.length && i < chars2.length){
           if(chars1[j] == chars2[i]){
               i++;
               j++;
           }else {
           	   //当前起点的下一个位置,即起点移动一位		
               i = i - (j - 1);
               j = 0;
           }
           if(j == chars1.length){//返回当前的起点
               return i - j;
           }
        }
        return -1;
    }

二、KMP算法

1.部分匹配表

部分匹配值就是前缀和后缀的最长共有元素长度。部分匹配表详情我就不做介绍了。其实质就是有时候字符串头部和尾部会有重复如"ABCDAB"之中有两个"AB",那么它的部分匹配值就是2。
如在字符串"ABCDABCC"中找到字符串"ABCC",我们利用"ABCD"匹配"ABCC"时肯定匹配失败,按暴力解法我们会以A的下一个位置即B为起点继续匹配,而利用部分匹配表,ABCD部分匹配值为0,我们可以直接从ABCC中的A位置开始新一轮的匹配,节省了大量时间。

2.获取字符串的部分匹配表

获取字符串的部分匹配表即获得该字符串的所有子串的部分匹配值,例如字符串"ABCDABD"的部分匹配表为{0, 0, 0, 0, 1, 2, 0},因此可以编写一个方法返回一个next数组(即部分匹配表),数组长等于字符串长度,next[i]表示以i字符结尾的字符串的部分匹配值
代码如下(示例):

 public int[] kmpNext(String dest){
        int[] next = new int[dest.length()];
        next[0] = 0;//字符串长度为1时部分匹配值为0
        //变量i为遍历字符串的索引,变量j为当前字符串的部分匹配值
        for (int i = 1, j = 0; i < dest.length(); i++){
            while (j > 0 && dest.charAt(i) != dest.charAt(j)){
            	//理解这里为什么时j = next[j - 1]很重要
            	//如果j != 0说明next[i - 1]的位置的部分匹配值不为0
            	//此时我们求部分匹配值是在i - 1的基础上进行匹配的
            	//如果此时不符合 我们因该回溯到next[j-1] 再重新匹配 
            	//这里不太好理解 因该结合具体实例来理解
            	//比如AABCDAAA next[] = {0, 1, 0, 0, 0, 1, 2, 2} 
            	//当i = 7时 此时j = 2 charAt(7)与charAt(2)显然不等 
            	//因为next[2 - 1] = 1 因此j = 1
            	//所以我们要回溯到charAt(7) 与charAt(1) 显然相等
            	//因此 next[7] = 2
                j = next[j-1];
            }
            if (dest.charAt(i) == dest.charAt(j)){
                j++;
            }
            next[i] = j;
        }
        return next;
    }

3.KMP算法

KMP算法代码如下(示例):

public int kmp(String s, String t){
	   int[] next = kmpNext(s);
       for (int i = 0, j = 0; i < t.length(); i++){
           while (j > 0 && t.charAt(i) != s.charAt(j)){
               j = next[j-1];//表示j处的字符不匹配 要回溯到next[j-1]处
           }
           if (t.charAt(i) == s.charAt(j)){
               j++;
           }
           if (j == s.length()){
           	return i - j + 1;
           }
       }
       return -1;
}

获取字符串s的部分匹配表即next数组,根据next数组,对字符串t和字符串s进行比较,找到s串在t串中的起始下标

总结

KMP算法常用于确定字符串t中是否包含字符串s、字符串t中字符串s的起始坐标、字符串t中包含几个字符串s等问题。
算法流程为:
(1)获取字符串s的部分匹配表
(2)根据字符串s的部分匹配表进行字符串s和字符串t的比较,根据题意获取答案
KMP算法的难点在于获取部分匹配表,理解j = next[j - 1]这处代码很关键。其实观察之后不难发现,获取部分匹配表的代码即kmpNext与kmp算法代码相似度非常高,KMP算法也是解决字符串问题的经典算法,应该熟练使用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值