一、暴力搜索法
假设目标匹配字符串为蓝色所示的字符串,源字符串为黄色所示的字符串。
暴力搜索的流程图:
示例代码:
public class ViolenceMatch {
public static void main(String[] args) {
String str1 = "BBC ABCDAB ABCDABCDABDE";
String str2 = "ABCDABD";
int index = violenceMatch(str1, str2);
System.out.println("str2第一次出现的位置为:" + index);
}
//暴力匹配算法
public static int violenceMatch(String str1, String str2) {
char[] s1 = str1.toCharArray();
char[] s2 = str2.toCharArray();
int s1Len = s1.length;
int s2Len = s2.length;
int i = 0;
int j = 0;
while (i < s1Len && j<s2Len) {
if (s1[i] == s2[j]) {
i++;
j++;
} else {
i = i - (j - 1);
j = 0;
}
}
if (j == s2Len) {
return i - j;
} else return -1;
}
}
二、KMP算法
假设目标匹配字符串为蓝色所示的字符串,源字符串为黄色所示的字符串。
1、计算源字符串的前缀表
-
首先得出该字符串所有的前缀
-
然后,根据得到的各个前缀,观察每个前缀子字符串,得出最长前后缀的公共字符数量。比如,abab的前缀ab,后缀ab是最长的,数量为2
-
最终得出前缀表,并对前缀表进行优化。索引为0的元素设置为-1,并将最后一个位置的元素舍去,其他位置的元素依次后移
2、使用KMP思想进行匹配
-
首先,和暴力搜索算法一样,对每一个字符进行匹配
-
结果,到了第四个字符开始,就出现了匹配不上的情形。按照暴力搜索的算法来看,就要往后整体移动一个单位,但是按照KMP算法来讲,以上图出现匹配不上的位置为例,当前位置对应的字符为b,此时前缀表中对应的元素为1,那么就要将pattern字符串中索引为1的位置,移动到未匹配到的字符的位置
好处就是能保证前面的字符已经是完整匹配上的,不需要重复匹配了。
-
继续进行匹配,如果不行就按照上面一个步骤的方法进行移动
-
再匹配,再移动进行判断
-
目前出现了这种稍微特殊一点的情形,当前未匹配到的字符所对应的前缀表中的元素值为-1。遇到这种情况的话,将源字符串pattern直接右移一个位置即可,将-1索引的位置对准当前未匹配到的字符的位置。
-
在此进行匹配,发现匹配完全正确
-
在第一次匹配完成之后,后面还会不会有第二次匹配的可能呢?所以需要继续匹配。
-
移动之后在进行判断,不行,就没有必要再比较了,因为已经到了target字符串的末尾了。
本示例对于KMP算法相比于暴力搜索算法的改良并不是非常明显。
可以拿Target:aaaaaaaab,Pattern:aaaab进行举例尝试。
三、KMP算法相关思路及代码
-
首先,计算pattern字符串的前缀表
-
根据计算并移位,得出最后的前缀表
-
观察第一个步骤得出的前缀表的元素值。比如,ABABCA这个前缀所对应的前缀表元素值为1,而它后面的那个前缀ABABCAB,所对应的前缀表元素值为2。其实,需要比较当前新增的这个字符,与ABABCA这个前缀所对应的前缀表元素值所对应的索引字符进行匹配,如果相同就将公共前后缀加1。如图所示:
-
但是,如果遇到那种后面的比较的字母不相同的呢?如下图所示:
-
显然,上面的字符串对应的公共前后缀为ABA ,而下面的那个字符串对应的公共前后缀为A。这种情况下,上面的这个字符串所对应的len为3,意味着前3个字符和后三个字符是相同的。咱们不能一个个的正着匹配,但是咱们可以斜着匹配啊。如下图所示:
-
代码示例:
public class KMPAlgorithm { public static void main(String[] args) { String str1 = "ABABABABCABAAB"; String str2 = "ABABCABAA"; int index = kmp_search(str1, str2); System.out.println(index); } //获取kmp前缀表 public static int[] prefix_table(String dest){ int[] next = new int[dest.length()]; next[0] = 0; int len = 0; int i = 1; while (i< dest.length()){ if (dest.charAt(i)==dest.charAt(len)){ len++; next[i] = len; i++; } else { if (len>0){ len = next[len-1]; } else { //此时len肯定为0了 next[i] = len; i++; } } } return next; } //将前缀表整体右移一位 public static void move_prefix_table(int[] next){ int length = next.length; for (int i = length-1; i >0 ; i--) { next[i] = next[i-1]; } next[0] = -1; } //kmp算法 public static int kmp_search(String source,String dest){ int n = dest.length();//n表示要匹配字符串的长度,用j表示(短的) int m = source.length();//m表示被匹配字符串的长度,用i表示(长的) //得到前缀数组 int[] prefix_table = prefix_table(dest); move_prefix_table(prefix_table); int i = 0,j = 0; while (i < m){ //在此输出结果 if(j==n-1&&source.charAt(i)== dest.charAt(j)){ return i-j; } if (source.charAt(i)==dest.charAt(j)){ i++; j++; } else { j = prefix_table[j]; if(j==-1){ i++; j++; } } } return -1; } }
本人表达能力和业务水平有限,望读者海涵。