您的位置:首页 > 编程语言 > Java开发

字符串匹配KMP(Java实现)

2019-03-12 21:57 106 查看

问题:返回目标字符串在源字符串中第一次出现的位置。

如str1=”abcabc”;  str2=”cab”;  则str2在str1中第一次出现的位置为2。

 

若使用暴力法(Brute Force), 则需要从str1的第2、3、4...个元素依次和str2比较。

假设str1个数为n,str2个数为m,则最坏需要比较(n-m+1)*m次,时间复杂度为O(n*m)。

例如:str1=”aaaa”   str2=”aaak”(str2依次与str1比较,第一轮比较4次,因为第二轮比较时str1为”aaa”,长度小于str2,因此一共比较4次)。

 

下面看一个例子:

[code]static int indexOf(char[] source,int sourceOffset,int sourceCount
,char[] target,int targetOffset,int targetCount, int fromIndex) {

//形参说明:source源字符串数组,sourceOffset源字符串偏移位置,sourceCount源字符串的元素个数
//target目标字符串数组,targetOffset目标字符串偏移量,targetCount目标字符串个数
//fromIndex查找位置的下标,从源字符串中查找目标字符串,并返回索引

//如果查找位置(下标)大于或等于源字符串(长度),则此时到达源字符串末尾。如果目标字符串为
//空,则返回整个源字符串,否则查找失败返回-1
if(fromIndex>=sourceCount) {
return targetCount==0? sourceCount:-1;
}

//如果查找索引小于0,则重置索引值为0
if(fromIndex<0) {
fromIndex=0;
}

//如果以上两个if不成立,即:查找索引小于等于源字符串的个数,并且索引大于等于0。
//此时如果目标字符串个数为0,则返回索引值
if(targetCount==0) {
return fromIndex;
}

char first=target[targetOffset];  //得到目标字符串中的第一个字符,并开始在源字符串中查找
//* 注意查找范围: fromIndex+sourceOffset到sourceOffset+(sourceCount-targetCount)
//1、注意,这里的fromIndex不一定从0开始
//例如:"abcde"是源字符串"abcabcde"的子串,此时源字符串偏移量为3(即偏移了3个位置)。
//则此时fromIndex=fromIndex+3=3,即需要判断的"abcde"是"abcabcde"从第4个位置开始的偏移串。
//2、如果目标字符串为"bca"个数为3, 源目标串"abcabcde"个数为8,此时需要比较的位置最多到source[5]
//因为如果偏移到'c',此时"cde"与"bca"长度相等,再偏移"de"便小于"bca"了。当然最后得到的结果5还需要加上偏移量(0)。
int max=sourceOffset+(sourceCount-targetCount);

for(int i=sourceOffset+fromIndex;i<=max;++i) {
//如果源字符串中第一个字符不等于目标串的第一个字符,那么将源字符串偏移一个字符继续判断,直到偏移到max为止
//如果还没找到则退出for循环,并返回-1
if(source[i]!=first) {
while(++i<=max&&source[i]!=first); //注意这里是先偏移再判断
}
//*找到了源字符串与目标字符串第一个字符相同的字符,继续判断下一个字符是否相等
if(i<=max) {
int j=i+1;//下一个字符
int end=j+targetCount-1;   //下一个字符一直到走完目标字符串长度的位置

//继续判断下一个字符是否相等,如果不等,判断是否为最后一个字符,如果不是则退出for循环,并进行下次循环
for(int k=targetOffset+1;j<end && source[j]==target[k];j++,k++);

if(j==end)
return i-sourceOffset;  //找到索引
}
}

return -1;
}

二、KMP

1、先看一个问题:str2="abcabcd"

求字符'd'最长前缀和最长后缀相等的最大匹配程度,前缀不包括最后一个字符,后缀不包括第一个字符:

比如:1) d的第一个前缀a,与第一个后缀c ----不相等

           2)d的第二个前缀ab,与第二个后缀bc----不相等

           3)d的第三个前缀abc,与第三个后缀abc----相等

           4)d的第四个前缀abca,与第四个后缀cabc----不相等

           5)d的第五个前缀abcab,与第五个后缀bcabc----不相等

不存在第六个,因为前缀包含了最后一个字符,后缀包含了第一个字符。

因此,结果为abc,最长前缀和最长后缀相等的最大匹配程度为3.

2、建立next[]数组

在str2中求每个元素的最长前缀与最长后缀相等的最大匹配程度

如:"ababac"      

因为第一个元素a没有前缀和后缀,因此规定为-1:next={-1};

第二个元素最长前缀a与最长后缀a相等,违反上述规则,规定为0:next={-1,0 };

第三个不存在匹配:next={-1,0,0};

第四个存在'a'与'a':next={-1,0,0,1};

第五个存在"ab"与"ab":next={-1,0,0,2};

第六个存在"aba"与"abc":next={-1,0,0,2,3};

3、求str2的next数组有什么用呢?

我们知道使用暴力法每次都要回溯,但没有记录有用信息:

如在"aaabac"查找"bac",在查找第一个位置后,我们肉眼很自然看到后面的aa均与b不匹配,但是使用暴力法会

逐一判断。在遍历源串的时候并没有记录有用信息,接下来解决这个问题。

 

如图,有str1和str2字符串,在字符’E’和字符’D’之前的字符均一一对应匹配。

Str1:

a

b

c

a

b

a

b

c

a

b

E

Str2:

a

b

c

a

b

a

b

c

a

b

D

此时发现Str2的最长前缀与最长后缀相等的最大匹配串是abcab

 此时我们寻找str1中str2的最长后缀与最长匹配程度最长的部分作为起点;

因为如果从str1第一个字符和str2第一个字符逐一匹配后,发现最后'E'与'D'不匹配。要想在str1中找到

与‘D’最长前缀(越长移动越远越省事),必须是‘D’的最长后缀(这里的最长前缀与最长后缀相等,next数组中有记录长度)

因此,将str2移动最长后缀的长度。又因为此时"abcab"与"abcab"匹配,因此从'E'与'a'开始继续匹配。

继续上述步骤知道找到最终串位置。

 

代码:

[code]	public static int getIndexOf(String s, String m) {
//如果源字符串s为空或目标字符串为空,或源字符串小于目标字符串 返回-1
//因为不考虑字符,目标字符串长度>=1
if (s == null || m == null || m.length() < 1 || s.length() < m.length()) {
return -1;
}
char[] ss = s.toCharArray();
char[] ms = m.toCharArray();
int si = 0;
int mi = 0;
int[] next = getNextArray(ms);
while (si < ss.length && mi < ms.length) {
//源字符串与目标字符串均不为空
if (ss[si] == ms[mi]) {
//字符匹配,继续判断下一个字符
si++;
mi++;
} else if (next[mi] == -1) {
//字符不匹配,目标字符串已经来到第一个位置,继续与源字符串下一个字符判断
si++;
} else {
//将目标数组偏移最长后缀个长度
mi = next[mi];
}
}//while
//如果遍历了整个目标数组,则找到,否则返回-1
return mi == ms.length ? si - mi : -1;
}

//生成next数组
public static int[] getNextArray(char[] ms) {
//如前讲述,第一个元素无前后缀
if (ms.length == 1) {
return new int[] { -1 };
}

int[] next = new int[ms.length];  //创建next数组
next[0] = -1;  //如前所述
next[1] = 0;   //如前所述
int pos = 2;   //next数组下标
int cn = 0;    //next中元素值
while (pos < next.length) {//遍历next数组
if (ms[pos - 1] == ms[cn]) {  //第i个元素前后缀匹配
next[pos++] = ++cn;
} else if (cn > 0) {
cn = next[cn];
} else {

next[pos++] = 0;
}
}
return next;
}

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: