在串和子串匹配的问题中,一般会存在两种算法:BF算法和KMP算法
比如在串:"QBFHILMEACHILK"中查看是否存在模式串"HILK"问题
首先,先说基础的BF算法,又叫做朴素匹配算法,用的是穷举的方法。
基于上面的问题,BF算法的思路是这样的:
(1)首先会将主串按顺序和模式串的第一个字母进行比较
(2)接下来继续匹配,仍然没有匹配上
(3)到这一步,终于可以有匹配的元素了
但是我们可以看到,在最后一个元素K的匹配上出现了问题,那么这个时候就是没有完全的匹配上,我们还需要继续往下走。那么这个时候我们需要怎么做呢?
我们应该回退到4号下标 i 的位置重新进行匹配,否则的话会遗漏掉一部分内容的匹配,会出现bug,所以在这里会有指针回退的处理
(4)接下来,按照上述流程往下走,一直到有串匹配。
指针回退位置:x = i - 已经匹配的个数 + 1;
int BF(std::string s,std::string p)
{
int i = 0; //遍历S主串
int j = 0; //遍历P模式串
while(i < s.size() && j < p.size())//有遍历完成的就退出
{
if(s[i] == p[j])
{
i++;
j++;
}
else
{
//在模式串中j的数值就是已经匹配的个数,所以指针i回退的位置应该是i-j+1;
i = i - j + 1; //下一趟主串需要匹配的位置
j = 0; //模式串回退到头部
}
}
return (j == p.size()) ? (i-j) : -1;
}
归根结底,BF算法就是利用了穷举,然后一一对比,这种方法是很浪费时间的。
——————————————————————
接下来是比较牛的KMP算法:
我们可以发现,之所以造成了时间的浪费,在于我们把所有的情况都列举出来了,那么我们有没有什么办法,可以去电一部分呢?我们是这样处理的,在KMP算法中,让主串的指针不回退,只让模式串的指针回退。这样就会节省很多的时间。
核心思想:为了找到可以省略的部分元素比较,比如下面元素的匹配:
省略1:
省略2:
省略3:
我们可以看到,省略1和2是因为开头就不匹配,最重要的就是省略3中的内容,我们怎么可以让主串中的指针不动,然后让模式串中的指针回退到相应的位置上呢?
如果,主串中的后缀 == 模式串中的前缀,如图所示:ABC == ABC,所以此时我们可以忽略这部分的比较,直接让主串中的指针从竖线部分往下比较就可以了,因为前面部分已经是比较过了。而在失配点之前都是已经匹配好的元素,所以主串中的后缀 == 模式中的后缀,所以有如下式子:
目的是寻找:主串中的后缀 == 模式串中的前缀
寻找的条件:主串中的后缀 == 模式中的后缀
总结:寻找模式串中的后缀和前缀相同元素的个数,就是模式串中指针每次返回的位置。
下面举一个例子来说明最大前缀和最大后缀的问题:"ABCABCD"
1.如果在A失配,我们统一给数值-1,因为就没有匹配,此时后缀和前缀相同元素的个数为-1;
2.如果在B失配,可以看到没有满足条件的,所以此时后缀和前缀相同元素的个数为0;
3.如果是第二个B失配,那么前面的ABCA,此时就是有一个元素A满足(最大前缀 = 最大后缀),所以此时后缀和前缀相同元素的个数为1.
4.如果是第二个D失配,那么前面就是ABCABC,此时后缀和前缀相同元素的个数为3.
那么这些元素就构成了一个数组next,到时候指针回退的位置就一目了然了。还有一个特殊的值就是-1,我们不可能回退到-1的位置,那么j == -1代表什么呢?
此时,失配点前面的元素没有一个是可以匹配的上的,这就是j == -1所表示的意思,那么我们的处理就是让两个指针都向后移动一下就可以了。
- 那么我们怎么求解这个next数组呢?即怎么求最大的匹配数呢?简单的串我们可以肉眼去看,但是怎么用通解去实现呢?
如果失配点之前K前缀和后缀相同,即:
t[0] t[1] … t[k-1]
t[j-k] t[j-k+1] … t[j-1]
这两者是相同的,如果此时再多加一个元素呢?
t[0] t[1] … t[k-1] t[k]
t[j-k] t[j-k+1] … t[j-1] t[j]
除了黑体外其他的部分都是一样的,所以我们只需要判断新添加的元素是否相等就可以了。
void GetNext(std::string t,int *next)
{
int len = t.size();
int j = 1;
int k = 0;
if(len == 0)
{
return ;
}
next[0] = -1;
//至少有两个元素才有处理的必要
if(len > 1)
{
next[1] = 0;
//因为每次求的是j+1的位置
while(j < len -1)
{
if(k == -1 || t[k] == t[j])
{
next[++j] = ++k;
}
else
{
//最精髓的就是这一句,画图
k = next[k];
}
}
}
}
int KMP(std::string s,std::string t)
{
int slen = s.size();
int tlen = t.size();
int i = 0;//主串的下下标
int j = 0;//模式串的下标
//用于存放指针回退下标
int* next = (int*)malloc(sizeof(int)*tlen);
GetNext(t,next);
while(i < slen && j < tlen)
{
if(j == -1 || s[i] == t[j]) //在这里我们要明白j == -1 的意义,当j == -1的时候,就是主串和模式串当前没有匹配的元素。
{
i++;
j++;
}
else
{
//模式串回退,主串不回退
j = next[j];
}
}
return (j == tlen) ? (i-j) : -1;
}