阮一峯老師的字符串匹配的KMP算法
kmp算法詳解(最透徹的沒有之一!)javascript
KMP
算法的原理部分,請看阮一峯老師的字符串匹配的KMP算法,這裏主要是代碼實現。html
假設給定模式串ABABCABAA
,要求出該模式串的最大長度表。java
i | 子串 | 最長先後綴 | 最長公共先後綴長度 |
---|---|---|---|
0 | A | / | 0 |
1 | AB | / | 0 |
2 | ABA | A | 1 |
3 | ABAB | AB | 2 |
4 | ABABC | / | 0 |
5 | ABABCA | A | 1 |
6 | ABABCAB | AB | 2 |
7 | ABABCABA | ABA | 3 |
8 | ABABCABAA | A | 1 |
很容易理解最大長度表每一個值的含義是模式串子串str[0,i]
的最長公共先後最綴長度。 如今,咱們來回顧一下這個表是怎麼得來的?
假設咱們目前獲得的表是下面這個樣子的,最後兩個是未知的,也是待求解的算法
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
pattern[i] | A | B | A | B | C | A | B | A | A |
len | 0 | 0 | 1 | 2 | 0 | 1 | 2 | ? | ? |
咱們已經知道i=6
的值是2
,對應的模式串字符爲A
,那麼咱們如何獲得i=7
的值呢?數組
咱們只須要將pattern[len]
和pattern[i]
進行比較便可,此時,咱們獲得pattern[len]===pattern[i]
,而後對len++
,記錄prefix_table[i]===len
,i
指針後移便可。函數
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
pattern[i] | A | B | A | B | C | A | B | A | A |
len | 0 | 0 | 1 | 2 | 0 | 1 | 2 | 3 | 1 |
如今,咱們已經獲得了i=7
的值,還剩下i=8
。咱們一樣按照上面的思路來思考。 比較pattern[8]--->A
與pattern[3]---->B
,出現失配的狀況,那此時咱們應該怎麼作呢,咱們的思路是縮短公共先後綴的長度,使prefix_table
發生側移。測試
代碼以下:優化
function generatePrefixTable(pattern){
var prefix_table = [];
var len = 0;// 最長公共先後綴長度初始化爲0
prefix_table[0] = len;
var i = 1;
var n = pattern.length;
while(i<n){
if(pattern[len] === pattern[i]){
len++;
prefix_table[i] = len;
i++;
}else{
if(len>0){ // 側移
len = prefix_table[len-1]; //縮小最長公共先後綴的長度
}else{ // 側移移動到len=0時,pattern[len]!=pattern[i],即pattern[0]!=pattern[i]
prefix_table[i] = 0;
i++;
}
}
}
return prefix_table;
}
複製代碼
next
數組function generateNextArr(prefix_table){
for(var i=prefix_table.length-1;i>0;i--){
prefix_table[i] = prefix_table[i-1]
}
prefix_table[0] = -1;
}
複製代碼
next
數組的含義是除當前字符外的最長公共先後綴的長度。spa
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|---|
pattern[i] | A | B | A | B | C | A | B | A | A |
next數組 | -1 | 0 | 0 | 1 | 2 | 0 | 1 | 2 | 3 |
好比對於ABABCABAA
來講,pattern[2]
以前的字符串AB
中有長度爲0
的公共先後綴,因此next[2]=0
;pattern[8]
以前的字符串ABABCABA
中有長度爲3
的公共先後綴。.net
kmp
搜索算法的實現
function kmp(str,pattern){
var prefix_table = generatePrefixTable(pattern)
generateNextArr(prefix_table)
var i=0; // str 指針
var j=0; // pattern指針
while(i<str.length && j< pattern.length){
if(str[i]===pattern[j]){
i++;
j++;
}else{
j = prefix_table[j] // 右移
if(j===-1){
i++;
j++;
}
}
}
if(j===pattern.length){
return i-j
}else{
return -1
}
}
複製代碼
kmp("bbc abcdab abcdabcdabde", "abcdabd") // 結果輸出爲15,正確
複製代碼
到目前爲止,已經實現了基本的kmp
算法,但仍是存在許多優化的地方。
next
數組,其實咱們能夠直接構建next
數組,節約時間和空間。function generateNextArr(pattern){
var i = 0;
var j = -1;
var next = []
next[0]=-1
while(i<pattern.length){
if(j===-1||pattern[i]===pattern[j]){
i++;
j++;
next[i]=j
}else{
j = next[j]
}
}
return next;
}
複製代碼
kmp
函數,咱們能夠改寫成下面的形式function kmp(str,pattern){
var next = generateNextArr(pattern)
var i=0; // str 指針
var j=0; // pattern指針
while(i<str.length && j< pattern.length){
if(str[i]===pattern[j] || j===-1){
i++;
j++;
}else{
j = next[j] // 右移
}
}
if(j===pattern.length){
return i-j
}else{
return -1
}
}
複製代碼
本篇完...