版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Never_Blue/article/details/71562846
2、串的模式匹配算法
串的查找操作也称作串的模式匹配操作,模式匹配操作的具体含义是:在主串(也称作目标串)中,从位置start开始查找是否存在子串(也称作模式串),如在主串中查找到一个与模式串相同的子串,则称查找成功;如在主串中为查找到一个与模式串相同的子串,则称查找失败。当模式匹配成功时函数返回模式串的第一个字符在主串中的位置,当模式匹配失败时返回-1。
2、1 Brute-Force算法
Brute-Force算法实现模式匹配的思想是:设主串为s="
s
0
s
1
…s
n-1
",模式串为t="
t
0
t
1
…t
n-1
"。
(1)从主串s的第一个字符开始和模式串t的第一个字符比较,若相等则继续比较后续字符。
(2)若主串s的第一个字符和模式串t的第一个字符比较不相等,则从主串s的第二个字符开始重新与模式串t的第一个字符串比较,若相等则继续比较后续字符。
(3)如此不断继续。若存在模式串t中的每个字符依次和主串s中的一个连续字符序列相等,则模式匹配成功,函数返回模式串t的第一个字符在主串s中的下标;若比较完主串s的所有字符序列,不存在一个和模式串t相等的子串,则模式匹配失败,函数返回-1。
public class BruceForce {
public static int bruceForce(String str , String subStr) {
int result = 0;
int len = str.length();
int lenSub = subStr.length();
if (lenSub > len) {
result = -1;
}
int i = 0 , j = 0;
while (i < len && j < lenSub) {
if (str.charAt(i) == subStr.charAt(j)) {
i ++;
j ++;
}
else {
i = i - j + 1;
j = 0;
}
}
if (j == lenSub) {
result = i - lenSub + 1;
}
return result;
}
public static void main(String[] args) {
String str = "cddcdc";
String subStr = "cdc";
int result = bruceForce(str, subStr);
if (result > 0) {
System.out.println("pos = " + result);
}
else if (result == 0) {
System.out.println("未找到!");
}
else if (result == -1) {
System.out.println("子串比主串长");
}
}
}
输出结果为:
pos = 4
这个算法简单并易于理解,但是有些情况下时间效率并不高。主要原因是:在主串和子串已有相当多个字符比较相等的情况下,只要有一个字符比较不相等,便需要把主串的比较位置(即函数中变量i的值)回退。设主串的长度为n,子串的长度为m,则Brute-Force算法在最好情况下的时间复杂度为O(m),在最坏的情况下的时间复杂度为O(n*m)。
2、2 KMP算法
1、Bruce-Force算法的缺点以及解决方法分析
KMP算法是Brute-Force算法基础上的改进算法。KMP算法的特点主要是,消除了Brute-Force算法的主串比较位置在相当多字符比较相等后,只要有一个字符比较不相等,主串位置便需要回退的特点。
分析Brute-Force算法的匹配过程可以发现,算法中的主串比较位置的回退并非一定比较。这可分为以下两种情况。
(1)第一种情况如上节的图中所示。主串s="cddcdc"、模式串t="cdc"的模式匹配过程为:当s0= t0,s1= t1,s1≠t1时,算法中下一次的比较位置为i=1,j=0,接下来比较s1和t0。但是因为t0≠t1,而s1=t1,所以一定有s1≠t0。所以此时比较s1和t0无意义,实际上随后可直接比较s2和t0。
(2)第二种情况,主串s="abacabab"、模式串t="abab"的第一次匹配如下图所示。此时有
s0= t0='a',s1= t1='b',s2= t2='a',s3≠t3。因此有t0≠t1,s1= t1,所以必有s1≠t0。又因有t0= t2,s2= t2,所以必有s2=t0。因此下面可以直接比较s3和t1。
总结以上两种情况可以发现,一旦si和tj比较不相等时,主串s的比较位置不必回退,主串的si可直接和模式串的t
k
(0≤k<j)比较,k的确定与主串s并无关系,k的确定只与模式串t本身的构成有关,即从模式串本身就可以求出k的值。
现在讨论一般情况。设s="
s
0
s
1
…s
n-1
",t="t
0
t
1
…t
m-1
",当模式匹配比较到s
i
≠t
j
(0≤i<n,0≤j<m)时,必存在"s
i-j
s
i-j+1
…s
i-1
"="t
0
t
1
…t
j-1
"。①若此时模式串"t
0
t
1
…t
j-1
"中不存在任何"t
0
t
1
…t
k-1
"="t
j-k
t
j-k+1
…t
j-1
"(0<k<j)形式的真子串,则说明在模式串
"
t
0
t
1
…t
j-1
"中不存在任何以为t
0
首字符的字符串与主串
"
s
i-j
s
i-j+1
…s
i-1
"中分别以
s
i-j
、
s
i-j+1
、...、
s
i-1
为首字符的字符串匹配,下一次可直接比较
s
i
和
t
0
。②此时若模式中存在如
"
t
0
t
1
…t
k-1
"="
t
j-k
t
j-k+1
…t
j-1
"形式的真子串,则说明模式中的子串已经和主串匹配,下次可以直接比较
s
i
和
t
k
。
2、KMP算法的改进
分析式"t0t1…tk-1"="tj-ktj-k+1…tj-1"(0<k<j),模式串中是否存在可相互重叠的真子串,只与模式串自身有关,与主串无关。因此,对于主串s="s0s1…sn-1",子串,t="t0t1…tm-1",可以首先计算出模式串t中每个字符的最大榛子穿的字符个数k。当模式匹配到si≠tj(0≤i<n,0≤j<m)时,随后要比较的主串的下标值不变,模式串的下边值即为k。
3、模式串中最大真子串的求法
模式串中每个字符的最大真子串构成一个数组,定义为模式串的next[j]函数。模式串的next[j]函数定义如下:
next[j]函数表示的是模式串t中是否存在最大真子串,以及最大真子串的字符个数k。这里之所以称为最大真子串,是因为:①求出的是所有子串中最大子串;②不允许k等于j。
next[j]定义中的第一种情况,是在模式串"t0t1…tj-1"中存在这样两个长度均小于j的字符串,其中一个字符串以t0为首字符,另一个字符串以tj-1为末字符,满足
"t0t1…tk-1"="tj-ktj-k+1…tj-1",
且这样的相等子串是所有这种相等子串中长度最大的。
next[j]定义中的第二种情况,是在模式串
"
t
0
t
1
…t
j-1
"
中不存在任何满足"t0t1…tk-1"="tj-ktj-k+1…tj-1"条件的真子串。
next[j]定义中的第三种情况,是当j=0是给出的特殊取值。当j=0是,令netx[j]函数取值为-1。在函数这几种,当netx[j]=-1时,令主串的下标和模式串的下标同时增1,即随后用子串的第一个字符和当前字符的下一个字符进行比较。
4、KMP函数设计
KMP函数中,当模式串t中的tj与主串s的si(i≥j)比较不相等时,若模式串t中不存在如上所说的真子串,有next[j]=0,则下一次比较si和t0,这是第一种情况;若模式串t存在真子串"t0t1…tk-1"="tj-ktj-k+1…tj-1",且满足0<k<j,则有next[j]=k,则下一次比较主串s的si和子串t的tk,这是第二种情况;当j=0时有next[j]=-1,则令主串的下标和模式串的下边同时增1,即随后用主串s当前字符的下一个字符和子串t的t0比较。
KMP函数可按如下方法设计:设s为主串,t为模式串,i为主串当前比较字符的下标,j为模式串当前比较字符的下标。令i的初值为start,j的初值为0。当si=tj时,i和j分别增1再继续比较;否则i不变,j改为next[j]值在继续比较。笔记哦啊过程中有两种情况:一是j增加到某个值或j退回到某个j=next[j]值时有si=tj,则此时i和j分别增1再继续比较;二是j退回到j=-1时,令主串和子串的下标各增1,随后比较si+1和t0。这样的循环过程知道变量i大于等于主串s的长度或变量j大于等于子串t的长度终止。
5、计算next[j]值的函数设计
next[j]值的计算问题是一个递推计算问题。设有next[j]=k,即模式串t中存在,其中k为满足等式的最大值"t
0t
1…t
k-1"="t
j-kt
j-k+1…t
j-1"(0<k<j),则计算next[j+1]的值有以下两种情况。
(1)若t
k=t
j,则表明在模式串中有
"t
0
t
1
…t
k
"="t
j-k
t
j-k+1
…t
j
",且不可能存在任何一个k'>k满足上式,因此有:next[j+1]=next[j]+1=k+1。
(2)若
t
k
≠t
j
,则可把计算next[j+1]值的问题看成是把模式串t'向右滑动至k'=next[k](0<k'<k<j)。若此时tk'=tj,则表明在模式串t中有 "t0t1…tk'"="tj-k'tj-k'+1…tj"(0<k'<k<j)有:next[j+1]=k'+1=next[k]+1。若此时tk'≠tj,则将模式串t'右滑到k''=next[k']后继续匹配。以此类推,直到某次比较有tk=tj(此即为上述情况),或某次比较有tk≠tj且k=0,此时有:next[j+1]=0。
6、代码示例
public class KMP {
public static int indexKMP(String str , String subStr , int start) { //str表示主串,subStr表示子串,start表示开始比较的下标
int[] next = getNext(subStr);
int i = start , j = 0 , result;
while ( i < str.length() && j < subStr.length() ) {
if ( (j == -1) || (str.charAt(i) == subStr.charAt(j)) ) {
i++;
j++;
}
else
j = next[j];
}
if(j == subStr.length())
result = i - subStr.length() + 1;
else
result = -1;
return result;
}
public static int[] getNext(String subStr) {
int j = 1 , k = 0;
int[] next = new int[subStr.length()];
next[0] = -1;
next[1] = 0;
while (j < subStr.length() - 1) {
if (subStr.charAt(j) == subStr.charAt(k)) {
next[j + 1] = k + 1;
j++;
k++;
}
else if ( k == 0 ) {
next[j + 1] = 0;
j++;
}
else
k = next[k];
}
return next;
}
public static void main(String[] args) {
String str = "cddcdc";
String subStr = "cdc";
int result = indexKMP(str , subStr , 0);
if ( result == -1 )
System.out.println("Not find!");
else
System.out.println("pos = " + result);
}
}
输出结果为:
pos = 4