深入理解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
運行結果:
在這裏插入圖片描述

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