KMP算法的JAVA实现

1.寻找最长前缀后缀

    如果给定的模式串是:“ABCDABD”,从左至右遍历整个模式串,其各个子串的前缀后缀分别如下表格所示:
    也就是说,原字符串对应的各个前缀后缀的公共元素的最大长度表为( 下简称《最大长度表》):

2.根据《最大长度表》求出next 数组

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

    而且,根据这个表可以得出下述结论

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

    把next 数组跟之前求得的最大长度表对比后,不难发现,next 数组相当于“最大长度值” 整体向右移动一位,然后初始值赋为-1。意识到了这一点,你会惊呼原来next 数组的求解竟然如此简单:就是找最大对称长度的前缀后缀,然后整体右移一位,初值赋为-1!

    换言之,对于给定的模式串:ABCDABD,它的最大长度表及next 数组分别如下:


    根据最大长度表求出了next 数组后,从而有

失配时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的next 值

    而后,你会发现,无论是基于《最大长度表》的匹配,还是基于next 数组的匹配,两者得出来的向右移动的位数是一样的。为什么呢?因为:

  • 根据《最大长度表》,失配时,模式串向右移动的位数 = 已经匹配的字符数 - 失配字符的上一位字符的最大长度值
  • 而根据《next 数组》,失配时,模式串向右移动的位数 = 失配字符的位置 - 失配字符对应的next 值
    • 其中,从0开始计数时,失配字符的位置 = 已经匹配的字符数(失配字符不计数),而失配字符对应的next 值 = 失配字符的上一位字符的最大长度值,两相比较,结果必然完全一致。

3.下面,我们来基于next 数组进行匹配。

    还是给定 文本串“BBC ABCDAB ABCDABCDABDE”,和模式串“ABCDABD”,现在要拿模式串去跟文本串匹配,如下图所示:

    在正式匹配之前,让我们来再次回顾下上文2.1节所述的KMP算法的匹配流程:

  • 假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置
    • 如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++,继续匹配下一个字符;
    • 如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]。此举意味着失配时,模式串P相对于文本串S向右移动了j - next [j] 位。
      • 换言之,当匹配失败时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的next 值,即移动的实际位数为:j - next[j],且此值大于等于1。
  • 1. 最开始匹配时
    • P[0]跟S[0]匹配失败
      • 所以执行“如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]”,所以j = -1,故转而执行“如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++”,得到i = 1,j = 0,即P[0]继续跟S[1]匹配。
    • P[0]跟S[1]又失配,j再次等于-1,i、j继续自增,从而P[0]跟S[2]匹配。
    • P[0]跟S[2]失配后,P[0]又跟S[3]匹配。
    • P[0]跟S[3]再失配,直到P[0]跟S[4]匹配成功,开始执行此条指令的后半段:“如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++”。
  • 2. P[1]跟S[5]匹配成功,P[2]跟S[6]也匹配成功, ...,直到当匹配到字符D时失配(即S[10] != P[6]),由于 j 从0开始计数,故数到失配的字符D时 j 为6,且字符D对应的next 值为2,所以向右移动的位数为:j - next[j] = 6 - 2 =4 位

  • 3. 向右移动4位后,C再次失配,向右移动:j - next[j] = 2 - 0 = 2 位

  • 4. 移动两位之后,A 跟空格不匹配,再次后移1 位

  • 5. D处失配,向右移动 j - next[j] = 6 - 2 = 4 位
  • 6. 匹配成功,过程结束。

    匹配过程一模一样。也从侧面佐证了,next 数组确实是只要将各个最大前缀后缀的公共元素的长度值右移一位,且把初值赋为-1 即可。


Java代码:

package JavaAlgorithm;
/**
 * 思想:每当一趟匹配过程中出现字符比较不等,不需要回溯i指针, 
 * 而是利用已经得到的“部分匹配”的结果将模式向右“滑动”尽可能远 
 * 的一段距离后,继续进行比较。
 * 时间复杂度O(n+m)
 */
public class KMP {
	public static void main(String[] args) {
		String s = "abbabbbbcab"; // 主串
		String t = "bab"; // 模式串
		char[] ss = s.toCharArray();
		char[] tt = t.toCharArray();
//		System.out.println(ss); 
//		System.out.println(tt); 
		System.out.println(KMP_Index(ss, tt)); // KMP匹配字符串
	}

	public static int[] next(char[] t) 
	{
		int[] next = new int[t.length];
		next[0] = -1;//0位置初始化为-1
		int i = 0;
		int k = -1;
		while (i < t.length - 1) 
		{
			if (k == -1 || t[i] == t[k]) //t[i]为前缀,t[k]为后缀。当与主串不匹配时:在next[i]的位置所应当回朔的位置为K
			{
				i++;//i一直加一,寻找与第一个字符相同的字符
				k++;
				next[i] = k;
			} 
			else  
			 k = next[k];//k重置为next[0]=-1
		}
		return next;
	}

	public static int KMP_Index(char[] s, char[] t) 
	{
		int[] next = next(t);
		int i = 0;//i为主串下标
		int k = 0;//k为子串下标
		while (i <= s.length - 1 && k <= t.length - 1) 
		{
			if (k == -1 || s[i] == t[k]) 
			{
				i++;
				k++;
//				next[i] = k; 对比next函数,没有这一步
			} 
			else 
				k = next[k];//这个地方子串右移k-next[k]
			                //当不满足IF判定时,则将当前匹配失败的回朔位置传递给K,使之进入到下一次运算。这样如果下一次一开始就匹配失败,则直接写入next[k]的回朔值就OK了。
		}
		if (k < t.length) 
			return -1; 
		else
			return i - t.length; // 返回模式串在主串中的头下标
	}
}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值