上一篇文章说到了,KMP就是通过分析模式串和当前匹配失败的字符串确定出最长前后缀来决定i+1和下一步要匹配的模式串的位置。
所以接下来就是研究怎么确定匹配失败之后i+1和模式串要匹配的位置是哪个?
有限确定状态机
在计算理论中,确定有限状态自动机或确定有限自动机(英语:deterministic finite automation, DFA)是一个能实现状态转移的自动机。对于一个给定的属于该自动机的状态和一个属于该自动机字母表求和的字符,它都能根据事先给定的转移函数转移到下一个状态(这个状态可以是先前那个状态)。
百度百科的解释。
在kmp的回退位置的确定过程中,也用到了这个算法。
接下来分析kmp算法的回退确定过程。
状态分为两种:
1、成功状态
成功状态下状态向前转移一位。
2、失败状态
失败状态就比较复杂了,需要考虑的是回退到开始呢,还是回退到某个最长前缀的下一位呢?
下面通过一张图来解释一下。
比如要匹配的模式字符串是ABABCB以下是关于这个字符串的状态机运行
1、每一步正确状态下的状态机运行
1、0的位置输入A,匹配状态指向1。
2、1的位置输入B,匹配状态指向2。
3、2的位置输入A,匹配状态指向3。
4、3的位置输入B,匹配状态指向4。
5、4的位置输入C,匹配状态指向5。
6、5的位置输入B,匹配状态继续+1大于长度代表结束。
2、失败状态下的回退
1、0的位置输入C不匹配,状态指向0.
2、1的位置输入C不匹配,状态指向0.
3、2的位置输入B不匹配,状态指向0.
4、3的位置输入A不匹配,状态指向1.
5、4的位置输入A不匹配,状态指向1.
6、5的位置输入A不匹配,状态指向1.
接下来问题是:为什么有的回退指向0有的回退指向1。
模式串是ABABCB
上文说过模式串的每个位置都有可能匹配到不同的字符,所以这里需要着重理解的是匹配到第3位(从0开始)输入了A
那么这个时候模式子串是ABAB。
这个时候我们找到的最长公共前缀是A。
运行状态也就是如下图1-1,所示。匹配失败知道是A,
这个时候发现模式的第一个字符串也是A于是可以知道这一位在模式串中可以
省略掉匹配直接将要匹配的字符串和模式的第1位(从0开始)进行匹配。
了解了以上这个概念之后,便开始考虑如何构造出这样一个状态机出来了。
构造之前先明确一下,第一点是每个位置都有可能匹配到不同的字符,第二点是每个位置的回退是根据前面已有的字符和现在正在匹配的字符确定。
以下是代码实现:
/**
* 生成dfa
*
* @param {String} patternString
*/
function dfa(patternString) {
let M = 256; //代表255个字符串
let N = patternString.length;
let dfa = [];
//初始化一个MxN的数组
for(let i=0;i<M;i++){
dfa.push([]);
for(let j=0;j<N;j++){
dfa[i].push(0)
}
}
//初始化一个二维数组
dfa[patternString.charCodeAt(0)][0]=1; //代表第1位匹配状态加一
//j是始终前进的 X代表j需要拷贝的位置
for(let j=1,X=0;j<N;j++){
for(let C=0;C<M;C++){
//拷贝状态
dfa[C][j]=dfa[C][X];
}
//这是关键的一步,设置当前j匹配的字符串的下一步正确位置
dfa[patternString.charCodeAt(j)][j]=j+1;
//读取dfa状态确定和前一位是否相同,不相同j+1 x依旧是0
//这样就可以保证始终在j和x位置相同时才可以下一位的比较
//因为存在255个字符的判断,所以每个字符的判断都有一个回退状态而dfa[patternString.charCodeAt(j)][X]记录的是每个字符在匹配成功之后应该到达的位置,比如最长前缀的话就会继续往下走,其他就不会
X=dfa[patternString.charCodeAt(j)][X];
}
return dfa
}