1.普通暴力算法
两个for循环,逐字对比,时间复杂度O(m*n);
2.KMP算法
理解步骤:
1.基于暴力算法。
string1和string2进行匹配,假如没匹配到,于x点被中断。
abadex
abadef
按照暴力算法的步骤,string1的指针i,应该退回到string1的第二个位置(第一个位置匹配失败了)。而string2的指针j,应该退回到string2第一个位置,重新匹配。
2.大胆假设优化方法
第一次匹配于x点被中断的时候,string1的指针i,我能不能直接跳到x点。
然后string2的指针j,跳到第1个位置,再继续匹配。
答案是大部分情况下是可以的,不过有一种情况下是会出问题的:
abcdabcdabce
abcdabce
这种情况下,string1的i在第一次匹配失败的时候,直接跳到d的地方,会错过中间abc开头的一串正确的匹配。
如果解决了这个特殊情况问题,KMP算法就完成了。
3.分析特殊情况出现条件
abcdabcdabce
abcdabce
这种特殊情况出现的条件,就是:
条件1:匹配失败,断点为x。
条件2:string2中,断点x前的字符串string2_before拥有重复开头的序列。
什么意思呢,就是string2中断点e之前的字符串abcdabc可以显示为abcdabc,后面的字符串会出现重复开头的字符串。
这两个条件,其他博客里有严格的数学论证,这里不作更多讲解。
4.如何处理特殊情况
abcdabcdabce
abcdabce
string1和string2进行匹配,出现匹配失败,断点为string1的d的,string2的e。
而这次匹配失败是一次上文的特殊情况,下一次匹配,string1中的i指针以及string2中的j指针接下来应该往哪放?
答案是:
string1中的i指针,直接跳到上一次匹配失败的中断点,string2中的j指针,根据条件来跳转。
i
abcdabcdabce
abcdabce
j
红色的字母i,j就是i和j匹配失败后,分别该跳转后的地方。
string1中的i的跳转和我们第二步骤的大胆假设中的跳转,没有很大区别,直接从第一个位置,跳转到中断点的位置。
重点就在于string2中的j的跳转位置,j跳转之后,需要满足我们的要求,string1的i点前和string2的j点前能够匹配。
这样碰到上述特殊情况,我们就转换成上面所示的情况,就能够继续完成我们第二步骤的大胆假设中的算法了。
而string2的j的跳转位置可以根据 string2中重复开头的子串的位置,推算出来。(其他博客有详细数学论证)
就能推算出一个getNext()函数,专门计算string2匹配失败的下次跳转位置。
5.getNext()函数
输入:string2字符串
输出:长度为string2.length的一个数组nums[];
数组nums[]的用法:
假设匹配,在j=5处匹配失败,j的下一次跳转位置就是 nums[5];
这个跳转的位置,是根据 string2中重复开头的子串的位置推算出来的。
6.实际算法
在大胆假设,string中的i可以在匹配失败后跳转到中断点x之后,出现了不符合假设的特殊情况。
在对特殊情况进行过处理之后,大胆假设就可以得到实现了.
Javascript实现:
let string1= "abcabcabdllabcabd";
let string2 = "abcabcabc";
console.log(getNext(string2 ));
console.log(KMP(string1,string2 ));
/**
* 28题
*/
//如果找不到重复开头的后续字串,会把nums[x]设置为-1,代表大胆假设的普通情况。
function getNext(ps) {
let p = ps;
let next = [];
next[0] = -1;
let j = 0;
let k = -1;
while (j < p.length - 1) {
if (k == -1 || p[j] == p[k]) {
next[++j] = ++k;
} else {
k = next[k];
}
}
return next;
}
function KMP(ts,ps) {
let t = ts;
let p = ps
let i = 0; // 主串的位置
let j = 0; // 模式串的位置
let 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留在匹配失败的断点不用移动
j = next[j]; // j跳转到getNext指定的位置
}
}
if (j == p.length) {
return i - j;
} else {
return -1;
}
}
然后代码用chrome的断点调试多跑跑,就会理解更透彻了。
标准说明
KMP算法是一种改进的字符串匹配算法,KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。时间复杂度O(m+n)。
在简单的一次匹配失败后,我们会想将模式串尽量的右移和主串进行匹配。右移的距离在KMP算法中是如此计算的:在已经匹配的模式串子串中,找出最长的相同的前缀和后缀,然后移动使它们重叠。
用next()函数提前计算出每个匹配失败的位置应该移动的距离。