KMP算法是解決字符串匹配的經常使用算法之一,也就是在主串(好比aabbccdd)中的子串(bc)定位問題。子串稱爲P,若是它在一個主串稱爲T中出現,就返回它的具體位置,咱們先來看看普通的字符串匹配是怎麼作的算法
思路:從左到右一個個匹配,若是這個過程當中有某個字符不匹配,將子串向右移動一位,繼續從左到右一一匹配。
數組
當匹配到如圖第四個字符位置後,匹配失敗,子串後移,繼續匹配 bash
public class Normal {
public static void main(String[] args) {
int index = bf("ABCABCEFG", "ABCE");
System.out.println(index);
}
public static int bf(String ts, String ps) {
char[] t = ts.toCharArray();
char[] p = ps.toCharArray();
int i = 0; // 主串的位置
int j = 0; // 子串的位置
while (i < t.length && j < p.length) {
if (t[i] == p[j]) { // 當兩個字符相同,就比較下一個
i++;
j++;
} else {
i = i - j + 1; // 一旦不匹配,i後退
j = 0; // j歸0
}
}
if (j == p.length) {
return i - j;
} else {
return -1;
}
}
}
複製代碼
這種方式是效率最低,匹配次數最多的狀況,接下來看KMP的解決思路函數
KMP在遇到下圖位置時,不會很無腦的把子串的j移動到第0位,主串的i移動到第1位,而後進行T[i]==P[j]的比較 ui
後移一位後子串前三位(ABC)和主串的T[1-4](BCA)確定是不匹配的,無需白白浪費這幾回比較
,最好應該是直接讓i不變,j==0,以下圖
KMP思想:利用前面匹配的信息,保持i指針不變,經過修改j指針,讓子串儘可能地移動到有效的位置。spa
整個KMP的重點就在於當某一個字符與主串不匹配時,咱們應該知道j指針要移動到哪?
3d
先用肉眼來看一下規律:指針
這時咱們發現規律了,其實就是要求當前j以前的字符串也就是ABCAB它的首尾對稱的長度最大長度也就是PMT值。code
PMT中的值是字符串的前綴集合與後綴集合的交集中最長元素的長度。orm
例如,對於」aba」,它的前綴集合爲{」a」, 」ab」},後綴集合爲{」ba」, 」a」}。
兩個集合的交集爲{」a」},
那麼長度最長的元素就是字符串」a」了,長度爲1,因此對於」aba」而言,它在PMT表中對應的值就是1。
再好比,對於字符串」ababa」,它的前綴集合爲{」a」, 」ab」, 」aba」, 」abab」},
它的後綴集合爲{」baba」, 」aba」, 」ba」, 」a」},
兩個集合的交集爲{」a」, 」aba」},其中最長的元素爲」aba」,長度爲3。
複製代碼
因此上面最後一個圖的狀況下,j位置以前的字符串的PMT值爲2,因此j的值變成2。
那麼好了接下來核心就是求得P串每一個下標元素對應的k值便可,由於在P的每個位置均可能發生不匹配,咱們要計算每個位置j對應的k,因此用一個數組next來保存,
next[j] = k,表示當T[i] != P[j]時,j應該變爲k。
求next數組代碼以下
public class Next {
public static int[] getNext(String ps) {
char[] p = ps.toCharArray();
int[] next = new int[p.length];
next[0] = -1;
int j = 0;
int k = -1;
while (j < p.length - 1) {
if (k == -1 || p[j] == p[k]) {
next[++j] = ++k;
} else {
k = next[k];
}
}
return next;
}
}
複製代碼
經過上面代碼能夠直接算出j爲0和1時的k,當j爲0時,已經沒法後退了因此設置爲-1初始化值,當j爲1時,它的前面只有下標0了,因此next[0]=-1,next[1]=0.
接下來就是兩種主要狀況了
if (k == -1 || p[j] == p[k]) { 第一種p[j] == p[k]
next[++j] = ++k;
} else { 第二種p[j] != p[k]
k = next[k];
}
複製代碼
p[j] == p[k]時,有next[++j] = ++k; 由於當在p[j-1]處匹配失敗後,j-1變爲k-1,從k-1處從新開始匹配,緣由就是他們共同有一個前綴A,因此當p[j] == p[k]後,他們就擁有了前綴AB因此k++;
有了next數組以後就一切好辦了,咱們能夠動手寫KMP算法了:
public class Kmp {
public static int KMP(String ts, String ps) {
char[] t = ts.toCharArray();
char[] p = ps.toCharArray();
int i = 0; // 主串的位置
int j = 0; // 模式串的位置
int[] next = getNext(ps);
while (i < t.length && j < p.length) {
if (j == -1 || t[i] == p[j]) { // 當j爲-1時,要移動的是i,固然j也要歸0
i++;
j++;
} else {
// i不須要回溯了
// i = i - j + 1;
j = next[j]; // j回到指定位置
}
}
if (j == p.length) {
return i - j;
} else {
return -1;
}
}
}
複製代碼
KMP算法是存在缺陷的,來看一個例子:好比主串是aaaabcde,子串是aaaaax,next值爲012345,當i=5時,以下圖:
public class Next2 {
public static int[] getNext(String ps) {
char[] p = ps.toCharArray();
int[] next = new int[p.length];
next[0] = -1;
int j = 0;
int k = -1;
while (j < p.length - 1) {
if (k == -1 || p[j] == p[k]) {
if (p[++j] == p[++k]) { // 當兩個字符相等時要跳過
next[j] = next[k];
} else {
next[j] = k;
}
} else {
k = next[k];
}
}
return next;
}
}
複製代碼