深入理解KMP算法

简介:
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)。
俗称:看猫片算法。
在这里插入图片描述
核心思路:
1. 主串不需要回滚,即 i 一直往后走。
2. 子串通过子串本身的一种性质,调整 j 的位置,再继续与 i 比较。
3. 时间复杂度由暴力破解的O(m * n)提高到O(m + n)。
算法分析:
首先,我们先求出模式串中每个子串的前缀和后缀的个数。知道了这个之后,我们相继的可以利用KMP算法思想,求出next值。(规定:第一个子串,没有前缀也没有后缀,永远等于0)
在这里插入图片描述0 A B
从上面,我们可以很清晰的看出来,每一个子串的前缀等于后缀的个数。知道了这个之后,为了方便利用kmp算法,我们需要把这些每个子串的next值往后移动一位,并把第一个子串的next值改为-1。
为什么我们要后移?
因为kmp算法是:找出当前字符的前面的子串的前缀和后缀的相同个数。因此我们需要让第一次计算的next值后移一位,才能和kmp算法保持一致。
在这里插入图片描述

import java.util.Arrays;
import java.util.Scanner;
public class Kmp {
	public static void main(String[] args) {
		Scanner sca = new Scanner(System.in);
		String s = sca.next();
		String t = sca.next();
		char[] SData = s.toCharArray();
		char[] TData = t.toCharArray();
		if(!kmpMatch(SData, TData)) System.out.println("抱歉没有找到你想要的!");
		sca.close();
	}
	/**
	 * 对主串s和模式串t进行KMP模式匹配
	 * @param SData 主串
	 * @param TData 模式串
	 * @return 若匹配成功,返回t在s中的位置(第一个相同字符对应的位置),若匹配失败,返回-1
	 */
	public static boolean kmpMatch(char[] SData, char[] TData) {
		boolean isMathch = false;
		int[] next = getNextArray(TData);   //get到TData的next数组值
		int i = 0;                          //主串的匹配下标
		int j = 0;                          //模式串的匹配下标
		while(i < SData.length) {           //循环条件——>主串到头,本次匹配结束
			if(j == TData.length - 1 && SData[i] == TData[j]) {
				isMathch = true;
				System.out.println("找到一个下标匹配成功,下标为:" + (i - j));
				System.out.println(Arrays.toString(SData));
				for(int h = 0; h < 3 * (i - j); h++) System.out.print(" ");
				System.out.println(Arrays.toString(TData));
				j = next[j];               //找到一个匹配成功的,j直接指向它的next值继续搜索
			}
			/**
			 * 如果j = -1,其实就是当前子串前后没有相等的,需要往后移动主串,模式串
			 * 如果SData[i] == TData[j],按照正常情况,主串和子串都往后移
			 */
			if(j == -1 || SData[i] == TData[j]) {
				i++;
				j++;
			}else                //如果搜索next数组没有到头,但是SData[i] != TData[j]
				                 //就让当前子串的下标指向它的next值
				j = next[j];
		}
		return isMathch;
	}
	/**
	 * 求出一个字符数组的next数组
	 * @param T 字符数组
	 * @return next数组
	 */
    public static int[] getNextArray(char[] T) {
    	int[] next = new int[T.length]; 
    	/**
    	 * 初始化next数组的第一位等于-1,第二位等于0
    	 * 因为前一个和前俩个字符并没有前后相同的字符,又因为在计算next数组和模式匹配时候,
    	 * next第一个等于-1:
    	 * 计算next数组:碰到当前next的值为-1,证明到头了,此时当前的模式串并没有前后一致的子串
    	 * 匹配模式串:遇到-1,此时主串和模式串都需要往后移
    	 */
    	next[0] = -1;                    
    	next[1] = 0;
    	int k;                               //存放next值
    	for(int j = 2; j < T.length; j++) {  //从第二个开始计算next数组
    		k = next[j - 1];                 //存放上一个字符的next值 
    		while(k != -1) {                 //判断next值是否到第一个,到第一个,证明当前子串并没有前后一致的
    			if(T[j - 1] == T[k]) {       //如果当前字符的前一个字符next值对应的字符和前一个字符相等,
    				next[j] = k + 1;         //当前的next值就可以增加1
    				break;		             //找到后,结束本次循环
    			}else {
    				//否则的话,当前子串,前后相同的最大长度,只能等于前一个字符的next值
    				//这时候我们需要再次循环判断上上一个的字符是否和自己的next值对应的字符相等 
    				k = next[k];             
    			}
    			//搜索完当前字符的前面所有字符的next值,都没有找到前后匹配的,循环结束前,把当前字符的next值置为0
    			next[j] = 0;  //当 k == -1 而跳出循环时,next[j] = 0,否则next[j]会在break之前被赋值
    		}
    	}
    	return next;
    }
}

测试数据:
ABABABABCABAAB
ABABCABAA
运行结果:
在这里插入图片描述
测试数据:
ABCABCABCABCAAABB
ABC
运行结果:
在这里插入图片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章