KMP总结(java实现)

文章讲解了KMP的流程,以及证明,并没有过于深入的剖析,适合入门,当流程懂了以后再去深究其内在会容易许多。

  • 问题描述:

字符串A:“abcabcfabcabce”
字符串B:“abcabce”
规定:A串>=B串的长度
要求:判断B串是否是A串的子串(连续的子字符串叫子串,不连续的叫子序列),如果是,返回B在A中的起始位置,如果不是,返回-1。

  • 简单办法:
    时间复杂度:O(M*N)
    以A串的每个字符为起点,依次与B串对比。
public static int getIndex(String str1,String str2){
		if(str1==null || str2==null || "".equals(str1) || "".equals(str2)){
			return -1;
		}
		int p1 = 0;
		int p2 = 0;
		while(p1<str1.length() && p2<str2.length()){
			if(str1.charAt(p1)==str2.charAt(p2)){
				p1++;
				p2++;
			}else{
				p1 -= p2-1;
				p2 = 0;
			}
		}
		return p2==str2.length()?p1-p2:-1;
	}
  • KMP算法:
    时间复杂度:O(M+N)

以下先说算法流程,后说原因,文字描述对应到代码中更容易看懂。
首先了解最长公共前缀后缀的概念

"前缀"指除了最后一个字符以外,一个字符串的全部头部组合,
"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。
举例:”ABCDA”的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],
共有元素为”A”,所以最长前缀后缀为1;

KMP算法需要求得字符串中每个字符位置前边的字符串最长公共前缀后缀。
举例:
在这里插入图片描述

  • 求得字符串每个位置最长公共前缀后缀值的思路:

首先定义 一个 int 类型数组,记为next ,长度和字符串长度相等。(作用:记录字符串中每个位置其前边字符串的最长前后缀)
对于数组中第一个位置人为规定为-1,第二个位置规定为0。
对于数组 i 位置值的求解逻辑:
1、取得next数组中 i-1 位置的值。
____如果 i-1 位置存在最长前后缀,如图1所示,比较字符串 i-1 和 j 位置的值( j 位置为 i-1 位置最长前缀的下一个),如果相同,则 i 位置值为 j + 1 (+1的原因:j是从0开始计数,而数组中记录的是前后缀字符的个数,后续+1操作同理),

____为什么 i-1 位置和 j 位置相同则 i 位置为 j+1 ? 如图 1 中的 i-1 位置来说,图中前缀和后缀标明的两坨东西是相等的,所以 i-1 位置如果和 j 位置相同,那么对于 i 位置来说他前边字符串的最长前后缀长度就是 j+1。

____如果不同,继续判断 j 位置是否存在最长前后缀,如果存在,如图2,比较字符串 i-1 和 t 位置的值是否相同 ( t 位置为 j 位置最长前缀的下一个),如果相同,则 i 位置的值为 t +1,依次类推,直到 i-1 和第一个位置的值对比,相同为1,不同为0。
2、如果所比较的位置不存在最长前后缀,则直接比较字符串 i-1 和第一个位置的值,相同为1,不同为0。

只需要求得较短字符串的next数组即可。

在这里插入图片描述
在这里插入图片描述

//求字符串每个位置的最长公共前缀后缀
public static int[] getNext(String str){
		if(str.length()==1){
			return new int[]{-1};
		}
		int[] next = new int[str.length()];
		next[0] = -1;
		next[1] = 0;
		int i = 2;
		int j = 0;
		while(i<next.length){
			if(str.charAt(i-1)==str.charAt(j)){
				next[i] = j+1;
				i++;
				j++;
			}else if(j==0){
				next[i] = 0;
				i++;
			}else{
				j = next[j];
			}
		}
		return next;
	}
  • kmp主流程:

设问题中长字符串为 str1,短字符串为 str2。

定义p1、p2两个指针,p1指向 str1 的开始,p2指向 str2 的开始。
1、循环判断字符串 str1 的p1位置和 str2 的p2位置的值是否相同,
如果相同,p1、p2同时往后移动一位,
如果不同,并且p2处于 str2 首字符,则p1往后移动一位,
如果不同,且p2没有处于B串首字符,p2取next[p2]的值,如图3。
当p1移动出A串或者p2移动出B串时循环结束。
结束后如果 p2 走完了str2,说明str2是str1的字串,未走完说明不是。
在这里插入图片描述

//kmp算法主体
public static int KMP(String str1,String str2){
		if(str1==null || str2==null || "".equals(str1) || "".equals(str2)  || str1.length()<str2.length()){
			return -1;
		}
		int[] next = getNext(str2);//获取next数组
		int p1 = 0;
		int p2 = 0;
		while(p1<str1.length() && p2<str2.length()){
			if(str1.charAt(p1)==str2.charAt(p2)){
				p1++;
				p2++;
			}else if(next[p2]==-1){//判断p2指针是否在第一个位置。
				p1++;
			}else{
				p2 = next[p2];
			}
		}
		return p2==str2.length()?p1-p2:-1;
	}
  • 分析:
  • 简单办法和kmp两种方式本质区别在遇到不匹配字符串时的处理逻辑

简单方法:以A串每个字符为首字符串与B串进行依次对比,当某个字符没有对上时,从下一个字符重新开始对比(如图4所示),中间不会对已比对过的信息进行利用。

在这里插入图片描述

KMP方法:

注意分清楚下方的p1、p2 和 1、2、3之间的区别
p1、p2指的是指针位置
1、2、3是指图5中蓝色字体的三段字符串

当 p1 与 p2 所指字符不相同时,会利用str2生成的next数组进行神操作,如图5所示。
当p1、p2不匹配时,如图5上部分所示位置,
p2 会根据next数组跳到next[p2]的位置,也就是图5下部分红色p2处,继续与p1进行比对。
为什么要这么跳?
1、图中的1、2、3(图5蓝色部分)是相同的,原因:1、2分别为p2的最长前后缀,而 p1 和 p2 之前的字符串都是相等的,所以 2 和 p1 前边等长的字符串3肯定也相同。
2、1和3相同,所以p1跳到3的开始和p2跳到1的开始进行对比的话,在1和3的范围内肯定也是相同的,所以p1不动,而p2跳到1结束位置的下一个位置,即next[p2]。(为什么p1不跳到3的前边的位置?后边有分析)

时间复杂度分析:
p2的移动并不是线性的,而是根据next数组进行跳跃式的移动。
p1从头到尾走过了字符串1,没有回头过。
所以时间复杂度为O(M+N)(M、N为两个字符串长度)。
在这里插入图片描述
为什么p1不跳到3的前边的位置?
以下用抽象的任意字符串举例:

图6标记说明:
黑色p1、p2代表当前位置的字符不匹配,而之前的位置都是匹配的
蓝色1、2表示p2的最长前后缀,3和1、2相同,都表示一段字符串
红色4、5、6表示某一段字符串
红色的 X 表示3之前的任意一个字符,红色p2表示第二个字符串的开始位置

当p1、p2不匹配时,p2回到字符串2开头位置(红色p2),p1跳到3前边的任意位置(X位置),
假设从x出发能和第二个字符串完全匹配,则肯定满足以下三个条件:
第一,x在3的前边,4表示从x出发到p1之间的字符串。
第二,4字符串和5字符串相等(要匹配整个字符串,即每一个小的字符串都必须匹配,4和5是等长的,4的头表示x的起点,5的头表示红色p2的起点)。
第三,4和6是相等的,因为黑色p1、p2不匹配时,其前边的字符都是匹配的(6表示黑色p2前边和4等长的一段字符串)。
结论:黑色p2的最长前后缀为1、2,而5、6又是相等的,此时发生了冲突,所以在next数组求解正确的情况下(1、2是最长前后缀),该假设不成立,即3之前不存在从x字符出发能匹配出第二个字符串。

在这里插入图片描述

以上图中的标识都是画了个大概,请不要用尺子来量,懂这个意思就好。。。。
如果有不对的地方还请指正,谢谢~
end…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值