一、KMP算法问题提出
- 有一个字符串 str1=“BBC ABCDAB ABCDABCDABDE”
- 对其进行判断,里面是否包含另外一个字符串 str2="ABCDABD"
二、KMP算法算法思想
2.1 具体移动步骤
-
第一步:用str1的第一个字符和str2的第一个字符去比较,不符合,关键词向后移动一位
-
第二步:重复步骤一,继续向后移动
-
第三步:一直重复直到str1有一个字符与str2的第一个字符符合为止
-
第四步:接着比较字符串和搜索词的下一个字符,是否还是符合,符合直接比,不符合进行下一步
-
第五步:此时如果继续重复步骤一,会出现“暴力匹配”,也就是继续一个一个往下比较,KMP算法思想就是利用已经知道的信息,不要把"搜索位置"拉回到以前,而是继续后移。
- 第六步:由第五步,我们可以了解到,其实我们进行第四步的时候已经比较过
ABCDAB
后面这些公共部分,所以对str2计算出一张一个叫 部分匹配的概念。
- 初步观察,重复的部分的值出现的部分有多少个共同字母一次后面值就进行加多少,
- 如:A出现过两次,则后面的A进行加1,
- 如:AB出现过一次,且AB拥有两个字母则加2
- 实际上:公式:移动位数 = 已匹配的字符数 - 对应的部分向后移动的位数
- 由此,我们再进行一次移位操作
- 未经过算法移动(仅仅向后移动一位):
- 经过算法移动(向后移动直到部分相同的):
- 继续移动(使用算法)
- 继续移动(使用算法)
2.2 部分匹配表
-
由上面的步骤,我们有一个问题,就是怎么样去确定到底应该向后移动多少步骤,由此引出部分匹配表的概念
-
前缀 与 后缀
- 如字符串:
"bread"
- 前缀:
b,br,bre,brea
- 后缀:
read,ead,ad,d
- 如字符串:
-
部分匹配表
- 就是“前缀”和“后缀”的 最长的共有元素的长度。
- 如:“ABCDABD”
- ”A”的前缀和后缀都为空集,共有元素的长度为 0;
- ”AB”的前缀为[A],后缀为[B],共有元素的长度为 0;
- ”ABC”的前缀为[A, AB],后缀为[BC, C],共有元素的长度 0;
- ”ABCD”的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为 0;
- ”ABCDA”的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA,A],共有元素为”A”,长度为 1;
- ”ABCDAB”的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为”AB”,长度为 2
三、KMP算法JAVA代码实现
public class KMPAlogrithm {
public static void main(String[] args) {
String str1 ="BBC ABCDAB ABCDABCDABDE";
String str2 ="ABCDABD";
//获取到一个字符串(字串)的部分匹配值
int []next =kmpNext("ABCDABD");//[0,1]
int result = kmpSercach(st1,str2,next []);
System.out.println("result = "+result);
}
//写出kmp搜索算法
/*
* str1 原字符串
* str2 子串
* next 部分匹配表
* @return 如果是-1,就是没有匹配到,否则返回第一个匹配的位置
* */
public static int kmpSerach(String str1,String str2,int[] next){
//遍历
for (int i = 0,j=0; i < str1.length(); i++) {
//需要处理,当str1.charAt(i) != str2.charAt(j)
//kmp算法核心点
while ( j > 0 &&str1.charAt(i) != str2.charAt(j)){
j = next[j -1];
}
if (str1.charAt(i) == str2.charAt(j)) {
j++;
}
if (j == str2.length()){
return i - j - 1;
}
}
return -1;
}
public static int[] kmpNext(String dest){
//创建一个next数组保存部分匹配值
int[] next = new int[dest.length()];
next[0] = 0;//如果字符串是长度为1部分匹配值就是0
for (int i = 1,j=0; i < dest.length(); i++) {
//当dest.charAt(i)!=dest.charAt(j),需要从next[j-1]获取新的j
//直到发现dest.charAt(i) ==dest.charAt(j)成立时候退出
//kmp算法核心点
while ( j > 0&&dest.charAt(i)!=dest.charAt(j)){
j=next[j-1];
}
//当这个条件满足时,部分匹配值就加1
if (dest.charAt(i)==dest.charAt(j)){
j++;
}
next[i] = j;
}
return next;
}
}