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前面,有相同前后缀a与a,前缀末位a从下标0开始,下标加一,这样表示相同前后缀的长度
表示 A[2] = 1,
模式串第四位字符b前面,有相同前后缀ab与ab,前缀末位b从下标1开始,下标加一,这样表示相同前后缀的长度
表示 A[3] = 2,
模式串第五位字符a前面,有相同前后缀aba与aba,前缀末位a从下标2开始,下标加一,这样表示相同前后缀的长度
表示 A[4] = 3 ,
模式串第六位字符b前面,有相同前后缀abab与abab,前缀末位b从下标3开始,下标加一,这样表示相同前后缀的长度
表示 A[5] = 4,
所以A数组=[0,0,1,2,3,4]
//创建匹配表
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;
}