再次阅读算法这本书后,对字符串匹配KMP算法有了新的认识。
算法可以分为2部分:1.构建有限状态机转换矩阵,别被这个名字吓到,这个矩阵的作用就像是你在走迷宫时的一份地图,它可以告诉你下一步无论怎么走会发生什么情况。
2.利用矩阵决定如何进行下一步的匹配。
先说说第二部分:假如你已经有了一份”地图“,该如何使用。
//使用DFA进行查找
public int search(String txt){
int i ,j, N = txt.length(), M = pat.length();
for(i=0, j=0; i<N && j<M; i++){
j = dfa[txt.charAt(i)][j]; //更新回退状态
}
if(j == M) return i-M;
else return N;
}
这里txt为文本字符串,pat为待查询的模式字符串。利用指针来分别标识文本字符串和待搜索字符串的位置:其中i代表文本匹配到了第i个字符,j代表了待匹配字符串相对于i的位置。这里显示了kmp算法的核心思想:利用已经匹配的“已知文本”,来尽量减少没必要的匹配,让只想文本字符串的指针i永远不回退,而相对的j的进行回退。所以j在这里也代表待匹配的字符(pat)和已知文本字符的最大重叠长度。重叠的越多,pat就需要回退的越多。
再看看第一部分:“地图”的生成。
int M = pat.length();
int R = 256;
dfa = new int[R][M];
dfa[pat.charAt(0)][0] = 1;
for(int X = 0, j=1; j<M; j++){
for(int c=0; c<R; c++){
dfa[c][j] = dfa[c][X]; //复制匹配失败状态
dfa[pat.charAt(j)][j] = j+1; //匹配成功状态
X = dfa[pat.charAt(j)][X]; //更新回退状态,用于下一次回退(当匹配到模式的第j个字符失败时,利用之前已经读取到了第1到j-1个字符,状态转换到第pat.charAt(j)行对应的状态——dfa[pat.charAt(j)][X])
}
}
这一步的思想是利用之前已经得到的状态来计算下一次匹配的状态。在每次构建新的状态时,利用回退状态X(匹配失败后的起点),将X状态复制到j状态,并修改j状态的匹配状态。最后更新X。