KMP算法及JavaScript实现
KMP模式匹配算法用于在文本串中快速查找模式串是否存在。
重点就在于构建最大相同前缀后缀数组。
当匹配出现不同时,模式串不是再次从头开始,而是利用前缀后缀相同的特点,从指定位置出发开始匹配。
示例如下:
-
在匹配过程中,D与空格不匹配时
-
普通做法,模式串指针指向起点,重新从头开始匹配:
-
使用KMP算法,利用前缀后缀的一致性。对于模式串中D的前一位B为止的字符串ABCDAB来说,最大相同前缀后缀长度为2,即前缀中的AB与后缀中的AB是相同的。利用这一特点,因为模式串后缀的AB与文本串中空格前面的的AB是匹配的,所以根据ABCDAB的最大相同前缀后缀长度来移动模式串上的指针如下:
注意,当不匹配出现时,说明前面部分的字符串是匹配的,所以要访问前面字符串的最大相同前缀后缀长度,不包括不匹配位置,即上例中,是利用ABCDAB的最大相同前缀后缀长度,而不是ABCDABD的。
注意, 这样的移动就是只是移动指针的位置。
思路:
1. 先对模式串进行遍历,建立模式串的最大相同前缀后缀数组。
前缀在这里表示为第一个字符开头,但不包括最后一个字符的字符串。
后缀为最后一个字符结尾,但不包括第一个字符的字符串。
最大相同前缀后缀长度表示前缀后缀相等的最大长度
分析:
- 单个字母的情况视为0, 如 ‘a’ 的最大相同前缀后缀长度就是0。
- 因为前缀不包括末尾字符,后缀不包括开头字符。'aa’的最大相同前缀后缀长度就是1,即前缀是第一个a,后缀是第二个a。
- 要注意不是回文串,比如 ‘aba’ 的最大公共前缀后缀长度也是1,因为ab不等于ba。
这里要对模式串的进行遍历,计算以每个字符为结尾时的最大相同前缀后缀长度,比如abaa,需要以分别求出以第一位的a结尾的最大长度,第2位b结尾的最大长度,第三位a结尾的最大长度,第四位a结尾的最大长度。
下图为一个模式串及其最大相同前缀后缀长度数组的例子:
这里计算的难点就在于如何快速计算出每个字符对应的最大相同前缀后缀长度,并不是对每个字符进行完全的遍历来求值,而是利用前面字符的最大相同前缀后缀长度来进行计算。
快速计算最大相同前缀后缀长度的方法如下所示:
对于模式串中的 a 来说,要计算以 a 为结尾的最大相同前缀后缀长度。
- 首先需要利用 a 之前一位的最大相同前缀后缀长度,即图中的红色部分;表示这一部分前缀与后缀相同。此时对于前缀区域后面一位b来说,如果b与a相同,则a的最大相同前缀后缀长度就是前一位的长度加1。
- 如果不相同,表示无法构成前一位的长度加1的最大相同前缀后缀。就需要利用红色区域即前一位前缀与后缀的相同性,就这个前缀后缀划分为更短的结构。即在相同的前缀后缀长度中寻找更小的相同前缀后缀,如图中黑色区域。
3. 而为了这种符合的黑色区域需要对红色区域进行分析展开。因为这里并不是回文串,而是最大相同前缀后缀,所以不是红色区域中的任意部分构成的黑色区域都满足相等条件。
即,可以看作在下图这种结构中找出相等的最大相同前缀后缀长度(黑色区域)
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/d5d6fd3aabef718e4d7d9f98b9e8e37c.png)
即图中上面表示后缀,下面的表示前缀,即需要找出满足相等条件的黑色区域,因为这里红色表示最大相同前缀后缀区域,因此上下的字符串是相同的,所以在前缀后缀中分别找子最大相同前缀后缀就等同于在一个部分(前缀或后缀)中找子最大相同前缀后缀,如下所示:
- 然后比较黑色区域所代表的最大相同前缀后缀的下一位是否与a相同,
如果相同,a处的最大相同前缀后缀长度就是黑色区域的长度加1。如果不相同,就对黑色区域进行同样的操作,展开求 子最大相同前缀后缀区域,然后下一位与a比较。直到展开到区域长度为1,即比较首字符与a还不相同时,就赋值0,表示最大相同前缀后缀长度为0。
图解表示如下:
图中的 i 和next数组可以忽视,思想就是这种一层层展开比较的结构。
实现代码:
// 计算next数组 在主函数中移动时取前一位的形式(相当于右移)
function caculateNext(strArr) {
// 先全部赋0
nextArr = new Array(strArr.length).fill(0);
let j;
// 从第一位也就是第二个字符开始比较</