KMP算法
KMP算法用於在文本串S內查找模式串P的出現位置 時間複雜度爲O(M + N)
最大長度表
前綴和後綴
前綴:除了最後一個字符以外,一個字符串的全部頭部組合
後綴:除了第一個字符以外,一個字符串的全部尾部集合
前綴後綴的最長公共元素長度
舉例說明,假如模式串爲P = "ABCABD"
模式串 | A | B | C | A | B | D |
---|---|---|---|---|---|---|
最大前綴後綴公共元素長度 | 0 | 0 | 0 | 1 | 2 | 0 |
- “A” :前綴爲空集,後綴爲空集,共有元素長度爲0
- “AB”:前綴爲[A],後綴爲[B],共有元素長度爲0
- “ABC”:前綴爲[A, AB],後綴爲[C, BC],共有長度爲0
- “ABCA”:前綴爲[A, AB, ABC],後綴爲[A, CA, BCA],共有長度爲1
- “ABCAB”:前綴爲[A, AB, ABC, ABCA],後綴爲[B, AB, CAB, BCAB],共有長度爲2
- “ABCABD”:前綴爲[A, AB, ABC, ABCA, ABCAB],後綴爲[D, BD, ABD, CABD, BCABD],共有長度爲0
next數組
next數組是KMP算法的核心部分,其中next[i]
的定義爲:在模板串P[0, i - 1]範圍內的前綴後綴最長公共元素長度
根據定義可以知道next數組可以由最大長度表向右移動一位得到
爲了計算方便next[0]
通常設爲-1
示例
模板串 | A | B | C | A | B | D |
---|---|---|---|---|---|---|
next[i] | -1 | 0 | 0 | 0 | 1 | 2 |
next[i+1]的推導
假如已經匹配到了next[i]
,接下來要得到next[i+1]
,要分兩種情況考慮:
1. P[i] = P[k]
模板串 | A | B | C | A | B | C | D |
---|---|---|---|---|---|---|---|
next[i] | -1 | 0 | 0 | 0 | 1 | 2 | 3 |
當i = 5
時,next[i-1] = 1
,前綴後綴最長公共子串長度爲1,此時k = 0
,P[k+1] = B
,P[i] = B
,可以看到前綴後綴最長公共子串爲"AB",長度爲2,所以next[5] = 2
結論:next[i+1] = next[i] + 1
2. P[i] != P[k]
模板串 | A | B | C | A | B | D | D |
---|---|---|---|---|---|---|---|
next[i] | -1 | 0 | 0 | 0 | 1 | 2 | ? |
當i = 5
時,next[i] = 2
,前綴後綴最長公共子串爲"AB",此時k = 2
,P[k] = C
,P[i] = D
,P[k] != P[i]
,但是因爲已知前面有相同的"AB",可以找P[0,k-1] = P[0, 1]
和P[i-k+1, i] = P[4,5]
的最長重合串,根據next數組的定義可知即爲求next[k]
k = next[k] = 0
,重新比較P[i] = C
和P[k] = A
,兩者不匹配,此時next[k] = -1
,說明P[0, i ]
的前綴後綴最長公共子串長度爲0,所以next[6] = 0
推薦看參考2的博客,這部分講的很詳細
結論:k = next[k]
個人對於k = next[k]
的理解:
當p[i] != p[k]
時,就需要找出比next[i] + 1
小的新前綴後綴最長公共子串長度。next數組本身的作用是找出[0,i-1]部分的前綴和後綴的最長公共子串長度,假設要找的新前綴後綴最長公共子串長度爲,相比於逐個減一去判斷,可以利用next數組做優化, k = next[k]
,判斷的前綴和後綴也相應的縮短到P[0,k]
和P[i-k,i]
,如果得到了新的前綴和後綴,結束迭代,否則繼續k = next[k]
直至k == -1
爲止
實現
public int[] createNext(String P) {
char[] p = P.toCharArray();
int[] next = new int[p.length];
int i = 0, k = -1;
while (i < p.length - 1) {
if (k == -1 || p[i] == p[k]) {
i++;
k++;
next[i] = k;
} else {
k = next[k]; // 重點是這一行
}
}
}
KMP算法
KMP算法就是利用next數組對暴力算法進行了優化。暴力算法在失配之後會回到最開始重新匹配,在S[i]
和P[j]
失配的時候,利用next數組將模板串P往右移動j - next[j]
思路
設置兩個變量i = 0
和j = 0
,文本串S和模式串P
- 如果
S[i]
和P[j]
匹配,文本串和模式串繼續向後匹配:i++;j++;
- 如果
S[i]
和P[j]
不匹配,保持i
不變,將模板串向右移動j - next[j]
:j = next[j];
- 如果
j = -1
,說明當前S[i]
開頭的字符串無法與P匹配,j++
讓模板串P回到開頭P[0]
,i++
讓S[i+1]
開頭的字符串與P開始匹配
實現
public int KMP(String text, String template) {
char[] textChar = text.toCharArray();
char[] templateChar = template.toCharArray();
int[] next = createNext(templateChar);
int i = 0, j = 0;
while (i < textChar.length && j < templateChar.length) {
if (j == -1 || textChar[i] == templateChar[j]) {
i += 1;
j += 1;
} else if (j != -1 && textChar[i] != templateChar[j]) {
j = next[j];
}
}
if (j >= templateChar.length) {
return i - t.length; // 匹配成功
} else {
return -1;
}
}
優化
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|---|---|
模式串 | A | B | C | D | A | B | D |
next[i] | -1 | 0 | 0 | 0 | 0 | 1 | 2 |
若i = 5
時匹配失敗,此時應該把i = 1
處的字符拿來繼續比較,但是P[5] == P[1] == B
,兩個字符一樣
實現
public int[] createNext(String P) {
char[] p = P.toCharArray();
int[] next = new int[p.length];
int i = 0, k = -1;
while (i < p.length - 1) {
if (k == -1 || p[i] == p[k]) {
i++;
k++;
if (P[i] != P[k]) {
next[i] = k;
} else {
next[i] = next[k]; // 相同情況下就繼續往前找
}
} else {
k = next[k];
}
}
}