菜单

用KMP算法实现strStr()

2015年7月26日 - 算法

strStr()函数的用途是在一个字符串S中寻找某个字串P第一次出现的位置,并返回其下标,找不到时返回-1。最简单的办法就是找出S所有的子串和P进行比较,然而这个方法比较低效。假设我们从S的下标0和P的下标0开始对每个字符进行比较,如果相等则下标增加,比较后面的字符。如果两者一直相等直到P的下标达到最大值,则表示在S中找到了P,并且第一次出现的位置为0,返回0,但如果在中间某个位置两个字符不相等时,这时S的下标要退回到1,P的下标回到0,重新开始比较。后来,有三个牛觉得这样不爽,于是他们搞了一个KMP算法,并以他们三人名字的最开始字符命名这个算法。这个算法的好处是在比较中的某个位置,两个串的字符不相等时,不需要S回退,而P的下标回退到某个值,然后继续和S当前失配的字符进行比较。

关键就是在这里了,P的下标要回退,退到哪儿啊?我们举个例子,S为“abcabcabeg”,P为”abcabe”,从下标0开始比较,一直到下标5才发现字符不相等,S对应字符为c,P对应字符为e。这时候,我们就考查P中下标5以前的字符串,即“abcab”。对于这个字符串,S下标5之前也存在同样的串,我们还发现这个串的前缀和后缀都有“ab”,即下标0、1和3、4分别组成的字符串相等,同时等于S串中下标3、4组成的字符串。因此,我们只需要把P的下标退到2的位置,然后跟S的下标5对应的字符继续比较即可,因为P的0、1下标对应字符和S的3、4下标对应的字符相等。如果对于每个失配的位置,我们都这样对P的下标进行调整,而S的下标继续往前而不后退,效率会提高很多。现在最大的问题就是怎么计算P失配时应该退回到的下标值,而从前面的分析中可以看出是对字符串的前后缀相等部分的长度来获取的,这样问题就变成了怎么编写代码来较为高效地计算在失配时P调整到的新下标值。对于P中每个位置,我们可以计算出在该位置失配时应该调整到的下标,并存放在一个next数组中,比如上面P在下标为5时失配,调整到的新下标为2,则有next[5]等2。

写到这里,发现好像讲得越来越乱了,还是贴我自己的代码吧,其实KMP最关键也是不太好理解的地方就是对next数组的理解和计算方法。

void fillNext(char* p, int* next)
{
if(!p) return;
int len = strlen(p);
int i = 1;
int j = 0;
for(int i = 0; i < len; ++i) next[i] = 0;
while(i < len-1)
{
if(p[i] == p[j])
{
i++;
j++;
next[i] = j;
}
else
{
if(j == 0)
{
i++;
next[i] = 0;
}
else j = next[j];
}
}
}

int strStr(char* haystack, char* needle) {
if(!haystack || !needle) return -1;
int len_h = strlen(haystack);
int len_n = strlen(needle);
if(len_n > len_h) return -1;

if(len_n == 0) return 0;
int* next = malloc(sizeof(int)*len_n);
fillNext(needle, next);
int i = 0;
int j = 0;
while(i < len_h)
{
if(haystack[i] == needle[j])
{
j++;
i++;
if(j == len_n)break;
}
else
{
if(j == 0)i++;
else j = next[j];
}
}
free(next);
if(j == len_n)
{
return i – len_n;
}
return -1;
}

发表评论