字符串匹配KMP(Java实现)
问题:返回目标字符串在源字符串中第一次出现的位置。
如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; }
- Java实现 字符串匹配 KMP 算法
- 算法导论-第32章-字符串匹配:Knuth-Morris-Pratt(KMP)算法C++实现
- 字符串匹配之KMP算法思路、原理与Java实现
- Java中使用正则表达式来实现字符串匹配
- 字符串匹配算法KMP实现
- 多模字符串匹配算法原理及Java实现代码
- Java中使用正则表达式实现字符串匹配
- kmp字符串匹配C实现
- Java中使用正则表达式实现字符串匹配
- 非KMP字符串匹配实现
- Java实现字符串匹配(基于正则)
- java实现字符串匹配问题之求两个字符串的最大公共子串
- Java实现算法导论中Rabin-Karp字符串匹配算法
- KMP字符串匹配C++代码实现
- 字符串匹配算法之___Sunday算法的java实现
- Java中使用正则表达式实现字符串匹配
- java通过堆栈实现字符串匹配
- java实现字符串匹配问题之求两个字符串的最大公共子串
- Java中使用正则表达式实现字符串匹配
- 字符串匹配(java)实现,普通的匹配和KMP算法 (参考)