簡介:
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)
從上面,我們可以很清晰的看出來,每一個子串的前綴等於後綴的個數。知道了這個之後,爲了方便利用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
運行結果: