KMP算法

本文分享自己对KMP算法的理解,尤其是最关键的next数组的求解。如果大家有任何问题或者我代码上有任何问题,请指出,相互交流。

1.最大匹配值介绍

一个字符串的最大匹配值:
	  		前缀(前n-1个字符由1->n-1个构成的所有字符串)  和 后缀(后n-1个字符,由第2到倒数第一个字符构成的所有字符串) 
	  		能匹配到的相同的所有字符串中最大的长度
	  例如:ABABA
	 		前缀   A AB ABA ABAB  后缀   A BA  ABA  BABA  
		    则最大匹配值就是ABA的长度  3

2.next数组介绍

next数组的含义,就是每个字符对应自己前面小子串的最大匹配值
 例如:ABABA
  next数组索引:				0	1	2		3		4
	  子串:					A	AB	ABA		ABAB	ABABA
	  前后缀相同的最大子串		-	-	A		AB		ABA
	  最大匹配值:			0	0	1		2		3
	  next[]				0	0	1		2		3

3.获取KMP的next数组-约定第一个数就是0
(不同于网上比较流行的next数组右移一位,约定为-1)

//获取KMP的next数组
private static int[] getNext(String source){
		//构建next数组
		int [] next = new int[source.length()];
		next[0] = 0;//当只有一个字符时,最大匹配值就是0 
		//对字符串进行遍历
		//i指向后缀  j指向前缀
		for(int i = 1,j=0;i<source.length();i++){
			//当对应的字符串不等时,应该先让j前移,去找到与i处字符对应相等的前缀中的索引,继续重新比较
			//通过next数组本身,利用当前字符前面的最大匹配值进行回转。也就是next[j-1]。注意越界判断
			while(j>0 && source.charAt(i)!= source.charAt(j)){
				//此时我们移动j索引  移动位数= 已匹配的字符数j-对应的部分匹配值next[j-1]。即(j-next[j-1])
				//j向前移动位数 j = j - (j-next[j-1]) = next[j-1]
				j=next[j-1];
			}
			
			//当对应的字符相等时,j自增
			if(source.charAt(i) == source.charAt(j)){
				j++;
			}
			//相等后,让此时i对应的next值,赋值为j.然后i后移,进入下一轮循环
			next[i] = j;
		}
		return next;
	}

4.解决字符串匹配问题-暴力匹配原理复习
字符串匹配-暴力匹配

5.KMP算法解决字符串匹配问题-对暴力匹配的优化

主要思路都在注释中,请对比暴力匹配原理,耐心理解代码


其中最关键 j=next[j-1] 求解思路:

  • 字符比较时,可以想象str1不动,比较不等时,索引不必全部回转,i不变,此时向右移动str2,已经比较过的字符不必再比较,找到重新开始比较的索引
  • 根据next数组进行对 j 进行一定调整,此时我们移动j索引 移动位数= 已匹配的字符数j-前一个最大匹配值next[j-1]
  • 搜索词向后移动位数就等于j向前移动位数,此时j的索引应该是当前索引-移动位数
  • j = j - (j-next[j-1]) = next[j-1]

//KMP算法利用next数组
	public static int kmpSearch(String str1,String str2,int [] next){
		//原理同暴力匹配,但是不等时,不必全部回转,根据next数组进行对j进行一定调整,已经比较过的字符不必再比较,i不变
		int i = 0;//遍历str1
		int j = 0;//遍历str2
		while(i<str1.length() && j<str2.length()){
			//先后顺序不能反,应该先调整j的索引位置,再去比较。
			
			//当两个字符遇到不等时,搜索词后移(j前移)。直到相等
			while(j > 0 && str1.charAt(i)!=str2.charAt(j)){
				//当遇到不等时,应该利用当前字符前面的最大匹配值进行回传。也就是next[j-1]
				//此时我们移动j索引  移动位数= 已匹配的字符数j-对应的部分匹配值next[j-1]
				//搜索词向后移动位数就等于j向前移动位数,此时j的索引应该是当前索引-移动位数
				//j = j - (j-next[j-1]) = next[j-1]
				j=next[j-1];
			}
			//当两个字符相等那就后移
			if(str1.charAt(i)==str2.charAt(j)){
				j++;
			}
			//i只是跟大循环相关,遍历大字符串,不受相等约束。
			//当两个串有相同的字符时,那就i j 一起后移,如果遇到不等的,那只是回转j,i保持不变,等待str2移动继续比较
			i++;
		}
		if(j==str2.length()){
			return i-j;
		}else{
			//结束循环没返回就是没找到了
			return -1;
		}
	}

6.主方法测试

public static void main(String[] args) {
		// TODO Auto-generated method stub
		String str1 = "BBC ABCDAB ABCDABCDABF";
		String str2 = "ABCDABF";
		
		//获取next数组
		int [] next = getNext(str2);
		System.out.println(Arrays.toString(next));
		
		//调用kmp算法进行遍历查找
		int index = kmpSearch(str1, str2, next);
		
		if(index != -1){
			System.out.println("匹配到的开始索引为"+index);
		}else{
			System.out.println("没有相关的匹配");
		}
	}