一.BF算法
暴力匹配(BF)算法是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较,直到得出最后的匹配结果。
我们来看看BF的代码实现:
以一组字符串(S=“abcdefg”,T=”cde”)为例:
int BF(const char *str,const char *sub,int pos)
{
assert(str!=NULL&&sub!=NULL);
int i=pos;//用i来记录S中字符的位置
int j=0;//用j来记录T中字符的位置
int lens=strlen(str);
int lensub=strlen(sub);
//当S不为空并且T不为空时,逐个进行字符的比较
while(j<lensub&&i<lens)
{
if(str[i]==sub[i])
//当S中第i个字符与T中第j个字符相等时,i向后移一个,j向后移一个
{
i++;
j++;
}
else
//当S中第i个字符与T中第i个字符不相等时,i回到前一个位置的下一个位置,j回到0号位置
{
i=i-j+1;
j=0;
}
}
if(j>=lensub)
//当j走完T的长度时,也就说明T在S中匹配成功
{
return i-j;
//此时返回字串在主串中的下标位置,此时i在该返回下标位置+j的长度位置,所以返回i-j,本例中返回下标3
}
//匹配失败,则返回-1,因为-1下标不存在,不能返回0下标
else return -1;
}
关于BF算法:
1.当第一个字符不相同时j也会继续向后比较,比如例子中的“abcdefg”和“def”,当“a”和“d”不相同时,则明显之后的两个字符及时相等也不是相同的子串。
2.每次j下标都要回到0号下标,当主串和字串匹配失败时,主串进行回溯会影响效率,回溯之后,主串与字串有些部分比较是没有必要的
综上:
这种简单的丢弃前面的匹配信息的算法,造成了极大的浪费和底下的匹配效率
二.KMP算法
在KMP算法中,对于每一个模式串都会事先计算出模式串的内部匹配信息,在匹配失败时最大的移动模式串,以减少匹配次数,这样就很好的解决了BF算法的缺陷
比如,当匹配失败后,最好是能够将模式字串尽量的右移和主串进行匹配,右移的距离在KMP算法中是这样计算的:在已经匹配的字串中,找到最长的相同的前缀和后缀,然后移动使他们重叠,这个位置就是j要回退的位置,这样j就不用每一次都回到0号位置了,每一次j回退的位置存储在一个数组里,称之为next数组
那么j回退的位置什么是最合适的呢
我们通过一组图来看一下:
根据以上理论,我们求得模式串的next数组:
以下是代码实现:
void GetNext(int *next,const char *sub)
{
next[0] = -1;
next[1] = 0;
int lensub = strlen(sub);
int i = 2;//当前的i
int k = 0;//前一项的K值
while(i < lensub)
{
if(k == -1 || sub[i-1] == sub[k])
{
next[i] = k+1;
i++;
k = k+1;
}
else
{
k = next[k];
}
}
}
int Kmp(const char *str,const char *sub,int pos)
{
int i = pos;
int j = 0;
int lens = strlen(str);
int lensub = strlen(sub);
int *next = (int *)malloc(sizeof(int) * lensub);
assert(next != NULL);
GetNext(next,sub);
while(j < lensub && i < lens)
{
if(j == -1 || str[i] == sub[j])
{
i++;
j++;
}
else
{
j = next[j];
}
}
if(j >= lensub)
{
return i-j;
}
else
{
return -1;
}
}
那么KMP算法可不可以优化呢?我们来看看以下例子:
以字符串”a b a b c”为例,当第二个a失配后,说明被匹配字符串一定不为a,这时候我们可以将当前值与串的sub[next[i]]值进行比较,如果相同,则将nextval[next[i]]值存入nextval[i],不同则将当前值的next的值存入nextval[i];
以一个模式串为例,求出他的next值和nextval值:
接下来就是代码实现:
(在next数组的基础上,我们实现KMP的优化)
void NextVal(int *nextval,const char *sub)
{
nextval[0]=-1;
nextval[1]=0;
int lensub=strlen(sub);
int i=2;
int k=0;
int *next = (int *)malloc(sizeof(int) * lensub);
assert(next != NULL);
GetNext(next,sub);
while(i < lensub)
{
if(k == -1 || sub[i] == sub[next[i]])
{
nextval[i]=nextval[next[i]];
i++;
k=k+1;
}
else
{
k = next[i];
}
}
}
三.时间复杂度
从BF算法到KMP算法的优化,更优化的就是为了实现更优化的代码,我们来看看这些排序的时间复杂度: