算法理解以简略为主,太多理论解释容易绕晕。
主要有以下三个数组:
-
匹配串:字符型
-
模式串:字符型
-
next[i]数组:int
一、next[i]数组说明(先了解这个比较好学)
next[i]是专门针对模式串的(功能我们后面结合实例介绍):
模式串 | a | b | c | d | a | b | d |
---|---|---|---|---|---|---|---|
next[i]数值 | 0 | 0 | 0 | 0 | 1 | 2 | 0 |
①首先我们了解前缀和后缀
前缀:(前缀后缀包括本身,但是在kmp算法不考虑本身)
abc的前缀包括{a,ab};
abcd的前缀包括{a,ab,abc};
可以看出前缀都是以第一个开始,依次增加,直到最后面的前一个(不包括最后一个)的集合。
后缀:
abc的后缀包括{c,bc};
abcd的后缀包括{d,cd,bcd};
可以看出后缀是都是以最后一个结尾,依次在前面增加,直到第二个(不包括第一个)的集合;
②然后我们学习next[i]数组的内容
next[i]是分析模式串结构的,以便kmp算法中的使用;
下标i:对应模式串中字符的位置,而实际的含义是指包含这个字符及之前的集合;(例如上面模式串i为3则是”abcd“)
next[i]: 关于数组的内容就是下标i所对应的字符串中,前缀与后缀相同的字符串最长长度;
例如i为5时,则在“abcda”中,前缀{a,ab,abc,abcd},后缀{a,da,cda,bcda},可知相同的就是“a”,则next[5]=1;
二、kmp算法实现
在了解next[i]数组后,我们结合实例学习next[i]的功能,及kmp算法优势实现:
匹配串(第一次匹配) | a | b | a | b | x(不匹配) | x | x | x |
---|---|---|---|---|---|---|---|---|
模式串 | a | b | a | b | c | d |
匹配串(第二次匹配) | a | b | a | b | x(开始) | x | x | x |
---|---|---|---|---|---|---|---|---|
模式串 | a | b | a | b | c | d |
kmp算法的优势体现就在第二次匹配。
(一)匹配的位置如何选择:
①当前状态:第一次匹配中模式串”abab“与匹配串已匹配,但是c不匹配;
②查next[i]数组:
-
查看next数组中c的前一个i(内容就是“abab”);
-
值为2(“abab”的前缀和后缀相同的是ab,所以值为2);
③匹配:
- 匹配串从上一次未匹配的x开始;
- 模式串则从模式串str[2]开始;(下标就是2就是从②中数组获取的)
④重复前三个步骤直到匹配成功或匹配串结束(若模式串还是没有匹配完则匹配失败)。
(二)为什么可以这么做(比较通俗说明):
相信这也是大家唯一存在的疑惑了;
匹配串 | a | b | c | a | b | x | x |
---|---|---|---|---|---|---|---|
模式串 | a | b | c | a | b | c |
①匹配串和模式串目前匹配的部分就是”abcab“
②观察”abcab“,发现前缀的”ab"向后平移最先匹配到后缀的“ab”;
③由于”abcab“是匹配串和模式串都有的部分,那我们只需要将模式串的前缀“ab"平移到匹配串的后缀"ab"处;
匹配串 | a | b | c | a | b | x | x | |
---|---|---|---|---|---|---|---|---|
模式串 | a | b | c | a | b |
④所以利用这一特征,我们不需要再匹配“ab”,而是从模式串的c开始,这也就是为什么next[i]数组中的数字就是模式串匹配开始的位置。
⑤注意我们next[i]数组中存的是前后缀的最长公共长度,所以是最大程度减少匹配。
⑥匹配串其实一直是匹配一位往后移一位,而这一位不匹配时,就根据next数组选择模式串的一位与它继续比较。
三、代码实现
原理大家都清楚了,代码部分可以自己根据例子理解学习。
next代码不懂戳这->next数组实现说明
public int[] getNext( String moshi ) {
int []next = new int[moshi.length()];
next[0] = 0;
for( int i = 1, j = 0; i < moshi.length(); i++ ) {
while( j > 0 && moshi.charAt(i) != moshi.charAt(j) ) {
j = next[j-1];
}
if( moshi.charAt(i) == moshi.charAt(j)) j++;
next[i] = j;
}
return next;
}
public int kmp( String pipei, String moshi ) {
int []next;
next = getNext(moshi);
int j = 0;
for( int i = 0; i < pipei.length(); i++ ) {
if( pipei.charAt(i) == moshi.charAt(j)) {
j++;
}else {
if( i != 0 ) i--;
if( j != 0 ) j = next[j-1];
}
if( j == moshi.length() ) return (i-moshi.length()+1);
}
return -1;
}
kmp算法是十分经典的算法,笔试中也常遇见,应掌握牢固,确保自己能随时用代码实现。