KMP算法证明及实现
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了KMP算法证明及实现相关的知识,希望对你有一定的参考价值。
KMP算法
一、普通的字符串匹配
平时我们在写普通的字符串匹配算法的时候,是拿着要匹配的串去匹配被匹配的串,字符逐个比较,当发现字符失配时,被匹配的字符串的指针要回到前一次开始匹配的指针的下一个位置。这里我们称要去匹配的字符串为模式串P,被匹配的字符串为主串S,即我们拿模式串P去匹配主串S,看看P是否是S的子串。
例如:主串S是“abcabdsfabcdfrt”,模式串P是“abcd”,开始匹配时,可以看到,S和P的字符串0、1、2位置的字符是相同的,到3位置时出现不匹配,这是根据我们以往的方法,我们将主串的指针回到刚开始匹配的位置的下一个位置,即回到主串的1位置,即字符b,再拿着模式串的0位置去匹配。以此类推,每次出现失配时,主串的指针都回溯到初始匹配位置的下一个位置,模式串的指针都回到模式串的0位置。
二、为何要用KMP算法
当我们的模式串P中,出现了连续相同的字符串时,例如P=“abcabx”,我们拿这个P和主串S=“abcabqqeeabcabxxxaxxaa”去匹配时。开始匹配可以看到,主串S的0、1、2、3、4位置和模式串的0、1、2、3、4位置的字符都是相同的,接着两个串的指针都下移,到了S的5位置的字符就和P的5位置的字符就出现吧不匹配,根据以往经验我们会将S的指针回溯到1位置接着和P的0位置进行匹配。这是我们发现,我们的P的0-1和3-4位置的字符串是一样的,而且S的0-4已经和P的0-4匹配是相同的,所以主串S的3、4位置和模式串P的0、1位置是相同的。
如果用主串的1、2位置与模式串从头匹配,则都是失配的,主串S的3、4位置和模式串P的0、1位置是相同的,所以我们就可以不回溯主串的指针,让模式串的2位置直接与主串的当前位置进行匹配即可。
三、KMP算法的数学推导
根据上面的情况,我们推广到一般情况。我们用i表示指向主串的指针,j表示指向模式串的指针,当主串的第i个字符和模式串的第j个字符失配时,主串中的第i个字符(指针i不回溯)应该与模式串中的哪个位置的字符再比较呢,假设我们的主串已经和模式串匹配比较到模式串的第k个字符了,那么模式串的前k-1个字符一定和主串的第i-k+1到i-1个字符是相同的,即
P1 P2 ……Pk-1=Si-k+1 Si-K+2…… Si-1
而已经得到的部分匹配结果是
Pj-k+1 Pj-k+2 ……Pj-1=Si-k+1 Si-K+2…… Si-1
由以上两式推导得出
P1 P2 ……Pk-1= Pj-k+1 Pj-k+2 ……Pj-1
反之,若模式串中存在满足上式的两个子串,则当匹配过程中,主串中的第i个字符和模式串中的第j个字符比较不相等时,仅需要将模式串向右滑动至模式串中的第k个字符和主串中第i个字符对齐,(此时,由于模式串中前k-1个字符和第i-k到i-1位置的字符都是对应相同的,所以模式串中前k-1个字符的子串P1 P2 ……Pk-1必定与主串中第i个字符之前长度为k-1的子串Si-k+1 Si-K+2…… Si-1相等)接着匹配从模式串的第k个字符与主串的第i个字符比较起继续进行。
四、求每一个位置对应的k(即next数组)
我们用next数组来存取模式串中每一个位置对应的k值,即next[j]表示当模式串中第j个字符和模式串中相应的字符失配时,在模式串中重新和主串中该字符进行比较的字符的位置。
根据三中的数学推导,得到了next函数的定义(假设字符串起始位置是1)
当j=1时,next[j]=0;
当j!=1时,next[j]=max(k|1<k<j且P1 P2 ……Pk-1= Pj-k+1 Pj-k+2 ……Pj-1)此集合不 为空时。若此集合为空,next[j]=1。
例,求模式串“abaabcac”的next数组
j |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
模式串 |
a |
b |
a |
a |
b |
c |
a |
c |
Next[j] |
0 |
1 |
1 |
2 |
2 |
3 |
1 |
2 |
四、代码实现
public class KMP {
/*
* Kmp函数实现寻找模式串str2在主串str1中的位置,返回值即为str2在str1中的位置,
* 如果str2不是str1的子串则返回-1.
*/
private static int Kmp(String str1,String str2){
//第一步 求next[j]数组
char[] strKey = str2.toCharArray();
int[] next = new int[strKey.length];
// 初始条件
int j1 = 0;
int k = -1;
next[0] =-1;
// 根据已知的前j位推测第j+1位
while (j1 < strKey.length - 1)
{
if (k == -1 || strKey[j1] == strKey[k])
{
next[++j1] = ++k;
}
else
{
k = next[k];
}
}
//打印一下我们的next数组
System.out.print("next[]的值 ");
for (int i = 0; i < next.length; i++) {
System.out.print(next[i]+1+" ");
}
System.out.println();
//第二步 根据求得的next数组进行字符串匹配
int j=0;//j指向模式串str2,i指向主串str1.
for (int i = 0; i < str1.length(); i++) {
if(j==str2.length()) return i-j;
if(str1.charAt(i)==str2.charAt(j)) j++;
else j=next[j]+1;
}
return -1;
}
//测试数据
public static void main(String[] args) {
String str1="12345abaabcac2356";
String str2="abaabcac";
int a=Kmp(str1,str2);
System.out.println(a);
}
}
以上是关于KMP算法证明及实现的主要内容,如果未能解决你的问题,请参考以下文章