KMP算法是什麼?
KMP算法是D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人們稱它爲克努特—莫里斯—普拉特操作(簡稱KMP算法)。
目的:爲了解決模式串匹配主串的時間複雜度最小,通俗的講,是有一個字符串A(主串),給定另外一個字符串B(模式串),求A串含有B串的位置
在講KMP算法之前,首先要知道前綴跟後綴的定義
前綴:有一個n個字符的字符串,前n個字符都是前綴。
eg:abcabc ,這個字符的前綴有a,ab,abc,abca,abcab ,5個前綴,最長一個是abcab
後綴:有一個n個字符的字符串,後n個字符都是後綴。
eg:abcabc ,這個字符的前綴有c,bc,abc,cabc,bcabc ,5個前綴,最長一個是bcabc
上面兩個例子,有相同並且最長的前後綴是abc
KMP有兩個關鍵的地方,解決這兩個,就解決了KMP算法
1.模式串跟主串是怎麼匹配的?
2.爲模式串建立匹配表
一,我們先來看第一個問題,模式串跟主串是怎麼匹配的?
假設
A串(主串):ckabawababab
B串(模式串):ababab
我們憑第一感覺,想一下應該是這樣匹配的,首先A串的第一個c跟B串的第一個a比較,發現不同,A串下移一位k,再與B串a比較,發現又不同,繼續A串下移一位a,再與B串a比較,發現相同,這是A,B串同時下移一位,繼續比較...,直到發現A串w跟B串的b不相同,這時A串不移動,B串回到原來的第一位,又重新開始比較,依次類推,最後算得結果ababab
這種方法有一個缺點,就是B串模式串老是重複計算,效率太低。
接下來,看KMP算法是怎麼做的?
一開始,跟之前的做法一樣,直到發現A串w跟B串的b不相同的時候,這時我們需要看B串b之前的相同最長前後綴是什麼,就是分析aba的前後綴,是a,這是我們不必要將B串模式串移動到原點,只需移動到B串第二位b那裏,爲什麼呢?
因爲A串w前面的a等於B串第三位的a,又因爲B串第三位的a跟B串第一位的a是相同前後綴,所以B串第一位的a必然等於A串w前面的a。
依次類推,最後得出結果
二,接下來,我們來解決第二個問題,爲模式串建立匹配表
模式串:ababab
我們用一個臨時數組A來存儲模式串的匹配表
首先模式串第一位字符a前面,沒有相同前後綴,即0
表示 A[0] = 0 ,
模式串第二位字符b前面,沒有相同前後綴,即0
表示 A[1] = 0 ,
模式串第三位字符a前面,沒有相同前後綴,即0
表示 A[2] = 0 ,
模式串第四位字符b前面,有相同前後綴a與a,前綴從下標0開始,下標加一,這樣表示相同前後綴的長度
表示 A[3] = 1 ,
模式串第五位字符a前面,有相同前後綴ab與ab,前綴b從下標1開始,下標加一,這樣表示相同前後綴的長度
表示 A[4] = 2 ,
模式串第六位字符b前面,有相同前後綴aba與aba,前綴a從下標2開始,下標加一,這樣表示相同前後綴的長度
表示 A[5] = 3,
所以A數組=[0,0,0,1,2,3]
//創建匹配表
function createPattern() {
var str = "ababab";
var prefix = [];
var subfix = [];
var patternMatch = [];
//匹配表
for (var i = 0; i < str.length; i++) {
var newstr = str.substring(0, i + 1);
if (newstr.length == 0) {
patternMatch[i] = 0;
} else {
for (var k = 0; k < i; k++) {
prefix[k] = newstr.slice(0, k + 1);
subfix[k] = newstr.slice(-k - 1);
if (prefix[k] == subfix[k]) {
patternMatch[i] = prefix[k].length;
}
}
if (!patternMatch[i]) {
patternMatch[i] = 0;
}
}
}
return patternMatch
}
//KMP算法完整代碼
function KMP() {
var str = "aabaabaaa";
var sourceStr = "bbbadcadcadcaaabaabaaa";
var prefix = [];
var subfix = [];
var patternMatch = [];
//匹配表
for (var i = 0; i < str.length; i++) {
var newstr = str.substring(0, i + 1);
if (newstr.length == 0) {
patternMatch[i] = 0;
} else {
for (var k = 0; k < i; k++) {
prefix[k] = newstr.slice(0, k + 1);
subfix[k] = newstr.slice(-k - 1);
if (prefix[k] == subfix[k]) {
patternMatch[i] = prefix[k].length;
}
}
if (!patternMatch[i]) {
patternMatch[i] = 0;
}
}
}
console.log(patternMatch, "匹配表");
//循環比對
var j = 0;
var result =''
for (var i = 0; i < sourceStr.length; i++) {
if (sourceStr.charAt(i) == str.charAt(j)) {
j++; //模式串下標加一
if (j == str.length) {
//比對找到,並返回結果的座標
console.log(i - str.length + 1, "結果");
result = i - str.length + 1
return;
}
} else {
j = j - patternMatch[j]; //根據匹配表後移動
if (j < 0) {
j = 0;
}
}
}
return result;
}