KMP算法理解
字符串匹配算法之KMP算法一直以来都很难理解,虽然知道要减少不必要的匹配,但是仍然不懂里面的思想,即使看着代码。后来看到(2)中博客中的解释才清晰了许多,不至于在什么DFA,前缀表,部分匹配表等概念中迷失自己。主要的指导思想在于当发生不匹配的时候如何更有效的利用现在已经匹配的字符串的信息来加速移动过程,部分匹配表的存在正是挖掘一个字符串中前缀和后缀中最长公共串,比如ABCABCD,在匹配'D'时失败,考察字符"ABCABC",得到pmt['ABCABC']=3,所以外围循环索引直接前进3个step。
下面利用这种最原始的想法来实现KMP,虽然在找最长公共前缀后缀时显得很low,但是很能说明最初的想法。
TODO:如何高效的计算部分匹配表??
Reference:
(1)http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm
(2)http://jakeboxer.com/blog/2009/12/13/the-knuth-morris-pratt-algorithm-in-my-own-words/
(3)http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html
字符串匹配算法之KMP算法一直以来都很难理解,虽然知道要减少不必要的匹配,但是仍然不懂里面的思想,即使看着代码。后来看到(2)中博客中的解释才清晰了许多,不至于在什么DFA,前缀表,部分匹配表等概念中迷失自己。主要的指导思想在于当发生不匹配的时候如何更有效的利用现在已经匹配的字符串的信息来加速移动过程,部分匹配表的存在正是挖掘一个字符串中前缀和后缀中最长公共串,比如ABCABCD,在匹配'D'时失败,考察字符"ABCABC",得到pmt['ABCABC']=3,所以外围循环索引直接前进3个step。
下面利用这种最原始的想法来实现KMP,虽然在找最长公共前缀后缀时显得很low,但是很能说明最初的想法。
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class KMP2 {
private int[] pmt; // Partial Match Table
private String pat; // the pattern string
// create the Partial Match Table from a pattern String
public KMP2(String pat) {
this.pat = pat;
int M = pat.length();
pmt = new int[M];
computePMT();
}
// 利用最蠢的方法来计算部分匹配表,前缀和和后缀共有元素的最大长度
public void computePMT() {
pmt[0] = 0;
for(int i=1; i<pat.length(); i++){
String current = pat.substring(0,i+1);
// 得到current的所有前缀和后缀,然后求交集,利用集合
Set<String> common = getCommonFromPrefixSuffix(current);
// 然后求得最大的公共元素长度
int longest = 0;
if(!common.isEmpty()){
Iterator<String> iter = common.iterator();
int cur = 0;
while(iter.hasNext()){
cur = iter.next().length();
if(cur > longest)
longest = cur;
}
}
pmt[i] = longest;
}
}
public Set<String> getCommonFromPrefixSuffix(String current) {
if(current.length() < 1)
return null;
Set<String> prefixs = new HashSet<String>();
Set<String> suffixs = new HashSet<String>();
int len = current.length();
for(int i=0; i<len-1; i++){
prefixs.add(current.substring(0, i+1));
}
for(int i=1; i<len; i++){
suffixs.add(current.substring(i, len));
}
prefixs.retainAll(suffixs);
//System.out.println(prefixs);
return prefixs;
}
// return offset of first match; N if no match
public int search(String txt) {
int M = pat.length();
int N = txt.length();
int i = 0, j=0;
while(i<N){
System.out.println("i:" + i);
j = 0;
while(j<M){
if(txt.charAt(i+j) != pat.charAt(j)){
// 移动位数 = 已经匹配的字符数 - 对应的此刻的部分匹配值
int offset = 0; // 后面体现了i右移
if(j>=1){
offset = j - pmt[j-1] - 1; //特别注意这里
}
i += offset;
break;
}else
j++;
}
if (j == M) return i; // found at offset i
i++;
}
return N; // not found
}
public static void test1(){
String pat = "ABABAC";
String txt = "BCBAABACAABABACAA";
KMP2 kmp = new KMP2(pat);
System.out.println(Arrays.toString(kmp.pmt));
int offset1 = kmp.search(txt);
System.out.println(offset1);
System.out.println(txt);
for (int i = 0; i < offset1; i++)
System.out.print(" ");
System.out.println(pat);
}
TODO:如何高效的计算部分匹配表??
Reference:
(1)http://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm
(2)http://jakeboxer.com/blog/2009/12/13/the-knuth-morris-pratt-algorithm-in-my-own-words/
(3)http://www.ruanyifeng.com/blog/2013/05/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm.html