问题描述
给两个字符串A,B,如A=“aaaaaaaaaaaaaaaaaaab”,
B = “aaaaaaaab”,现在要求B是否是A的字串。
普通方法(O(nm)),对两个字符串进行双重枚举进行匹配知道找到A字符串中的B字符串。
for(i = 0;i < n;i++)
{
int tf = 0;
for (k = 0;k < m;k++)
{
if (A[i] != B[k])
{
tf = 0;
break;
}
}
if (tf)
break;
}
KMP小忆
KMP算法是通过分析模式字符串,预先计算每个位置发生不匹配的时候,所需跳转到的下一个比较位置,整理出来一个next数组,然后在上面的算法中使用。
其达到的最终效果是,避免记录字符串A的指针i回溯,从而节省一个循环,也就是把O(mn),降为O(m)。
因此,也就可以说,KMP就是对O(nm)算法的改良,因此,其改良的地方,就是其关键。也就是说next就是KMP的精华所在。
犹抱琵琶的next函数
寒假时总结的模板
void kmp (char *T,int next[])
{
int j = 0,k = -1;
next[0] = -1;
while (T[j] != '\0')
{
if (k == -1 || T[j] == T[k])
{
j++;
k++;
next[j] = k;
}else
k = next[k]; //相当于在求next的时候利用已求出的next
}
}
这个是非改进版的求next的函数。其next数组记录的数据x代表这个元素的位置,代表与整个字符串T的前x个字符相同。
不能小看next
next的使用不仅仅是其储存的内容的或用上,甚至不能小瞧在求next数组的时候其产的中间值。
记得有一个题,就是让求字符串中循环节的长度,记得以前做类似的题目的时候是用的双重枚举,如果用KMP的话,也是可以省掉的一层循环。
做这题求next的代码如下:
int fnext (char *T,int next[])
{
int j = 0,k = -1;
next[0] = -1;
while (T[j] != '\0')
{
if (k == -1 || T[j] == T[k])
{
j++;
k++;
next[j] = k;
}
else
k = next[k];
}
if (k == 0)
return -1;
return j - k;
}
其中j指针指的是主字符串(不回溯的指针),k字符串指的是辅字符串(可以回溯),当next数组构建完成的一瞬间,想象一下j和k的样子,现在j指向了主字符串的最末尾,而k不一定指向哪,但可以肯定的是,如果T[j] != T[k],k一定不等于0,否则,k指向的位置x,一定会让字符串后x位与前x位相等。
也就是说,如果存在循环节,其循环节的长度就是j - k。
KMP带来的反思
KMP使用一个next数组让一种O(nm)的算法变为了O(n),有点用空间换时间的味道,像这种用空间换时间还有什么可以举一反三的吗?
KMP是利用空间,避免了在双重枚举的时候,一个指针的反复回溯,减少了一层循环。
那么,在做模拟题,尤其涉及到枚举的模拟题的时候,是不是在时间吃紧的情况下可以考虑通过存储某些东西,来去掉一层循环呢?