此算法的作用是查找给定子串在母串中的位置
BF算法
一种简单但效率不高的算法
int BF(string s, string t)
{
int i = 0;
int j = 0;
int lens = s.length();
int lent = t.length();
while (i < lens && j < lent)
{
if (s[i] == t[j])
{
i++;
j++;
}
else
{
i = i - j + 1;
j = 0;
}
}
if (t[j] == 0)
{
return i - j + 1;
}
else
{
return 0;
}
}
总体思路是如果子串和母串的其中一个字符匹配,就继续匹配下一个字符,如果遇到不相同的,则子串的下标回到最开始的位置,母串的下标也回溯,直到某一个串的下标到最后了。就进行判断,如果是子串到最后了,说明子串全部匹配成功,则返回第一个字符的下标。反之如果是母串到最后了,就说明子串没匹配完,匹配失败,返回0。
注意我这里的位置是从1开始的所以子串不在母串中返回0。
因为匹配失败时,两个下标都要回溯,导致效率低下,那么能不能减少回溯的次数或程度呢?
答案显然是可以的,就是KMP算法。
KMP算法
我先放代码
int KMP(string s, string t)
{
int i = 0;
int j = 0;
int lens = s.length();
int lent = t.length();
int next[10000] = { 0 }; ///
Find_Next(t, next); ///
while (i < lens && j < lent)
{
if (j == -1 || s[i] == t[j]) ///
{
i++;
j++;
}
else
{
j = next[j]; ///
}
}
if (t[j] == 0)
{
return i - j + 1;
}
else
{
return 0;
}
}
大体上和BF差不多,有不同的地方我在后面加了一些斜杠。
我先提一个细节:为什么要搞一个int变量装串的长度呢,不用行不行呢?现实是不行!因为string的求长度函数的返回值是无符号int,而 j 有可能 为-1,两者比较会出错。
KMP的思路和BF差不多,只是回溯的时候只回溯 j,i 不动,为啥可以这样呢?
假设有
这样一个子串DABCDABD
这样一个母串DABCDABCDABD
在进行匹配时,前面都很顺利就是最后一个字母不对如果是BF算法,那就会全部回溯到最开始的时候,但这样很费时间,我们仔细观察子串和母串的规律
DABCDABD
DABCDABCDABD
这两个蓝色片段是相同的,那我可不可以 i 保持不动,j 回溯到紫色c的位置然后继续匹配呢,实际上这样节约了时间,而且这种情况不是巧合,回溯到哪里是可以预知的,具体就是 j 回溯到next(j)的位置,可以看看上面的代码。
现在想想这么知道有哪些片段相同呢。
在一开始的时候
DABCDABD
DABCDABCDABD
当 i 指向最后一个D和 j 指向红色C时,前面的绿色片段是相同的 那我只要保证子串中蓝色的DAB和绿色的DAB相同,就可以保证母串中绿色DAB,和子串中蓝色DAB相同了(a=b,b=c, 所以a=c)。
所以next数组的数据是说明在 j 位置时,子串前面最大有多少字母相同。(这里规定 j=0时,next(j)=-1)
给一些例子帮助理解
请输入模式串:A B C D A B C
next数组为: -1 0 0 0 0 1 2
在第二个B时前面的A和后面A相同。
第二个C时前面的AB和C后面的AB相同。
请输入模式串:A A A A A A B
next数组为: -1 0 1 2 3 4 5
请输入模式串:A B A C D A B A B C
next数组为: -1 0 0 1 0 0 1 2 3 2
所以Find_next函数就是建立一个这样的next数组。
这里也附上代码
void Find_Next(string s, int next[])
{
int max = s.length();
int i = 0;
int j = -1;
next[0] = -1;
while (i <= max)
{
if (j == -1 || s[i] == s[j])
{
i++;
j++;
next[i] = j;
}
else
{
j = next[j];
}
}
}
跟KMP算法竟然长得差不多。