KMP和朴素匹配解决的问题:
- 字符串匹配。给你两个字符串,寻找其中一个字符串是否包含另一个字符串,如果包含,返回包含的起始位置。
如下面两个字符串:
char *str = “abcababcabc”;
char *ptr = “abcbac”;
str有一处包含ptr
在str的下标5处开始包含ptr。
问题很简单,下面来分别介绍两种算法:
朴素匹配算法说明:
- 朴素匹配又称暴力匹配,方法是每次匹配一个字符。简单来说就是str的每一个字符作为子串的开头,与要匹配字符串ptr进行匹配。对主串str做大循环,每个字符开头做strlen(ptr)长度的小循环,直到匹配成功或全部遍历完成为止。如图:
-
朴素匹配代码实现:
int BF(const char *str, const char *ptr)
{
int lenstr = strlen(str);
int lenptr = strlen(ptr);
int i = 0;
int j = 0;
while(i < lenstr && j < lenptr)
{
if(str[i] == ptr[j])
{
i++;
j++;
}
else
{
i = i - j + 1;
j = 0;
}
}
if(j == lenptr)
{
return i-j;
}
return -1;
}
KMP算法说明:
由朴素匹配可知,我们从目标字符串str(假设长度为n)的第一个下标选取和ptr长度(长度为m)一样的子字符串进行比较,如果一样,就返回开始处的下标值,不一样,选取str下一个下标,同样选取长度为n的字符串进行比较,直到str的末尾(实际比较时,下标移动到n-m)。这样的时间复杂度是O(n*m)。
KMP算法:可以实现复杂度为O(m+n)
怎样提升效率?
充分利用了目标字符串ptr的性质(比如里面部分字符串的重复性,即使不存在重复字段,在比较时,实现最大的移动量),str的下标不回退,回退ptr的下标到最适位置来减少匹配的次数。如图:
这里附上《大话数据结构》一书中对KMP算法的推演:
如何确定ptr回退的最适位置?
这里我们要实现一个函数getnext()来得到一个next数组,该数组的值就为ptr回退的最适位置。
next数组的含义就是一个固定字符串的最长前缀和最长后缀相同的长度。求解的规则为:在目标字符串中存在两个真子串一个以0位置开始,另一个以j-1位置结束,两真子串的长度就为next[j]的值。
比如:abcjkdabc,那么这个数组的最长前缀和最长后缀相同必然是abc。
cbcbc,最长前缀和最长后缀相同是cbc。
abcbc,最长前缀和最长后缀相同是不存在的。
**注意最长前缀:是说以第一个字符开始,但是不包含最后一个字符。
比如aaaa相同的最长前缀和最长后缀是aaa。**
对于目标字符串ptr,“abcabc”,长度是6,所以需求出next[0],next[1],next[2],next[3],next[4],next[5]的值:
我们通过一个复杂一点的例子来解释next数组的求解方法:
首先我们规定next[0] = -1 、next[2] = 0,我们通过一个图来体现next数组的求解:
- 由图中的例子我们求得ptr的next数组为next[]={-1, 0, 0, 1, 2};
代码实现:
void Getnext(const char *ptr, int *next, int len)
{
next[0] = -1;
next[1] = 0;
int i = 2;
int k = 0;
while(i < len)
{
if(k == -1 || ptr[i - 1] == ptr[k])
{
next[i++] = ++k;
}
else
{
k = next[k];
}
}
}
next数组的求解过程用语言很难表达出来(我基本都是靠自己理解过来的),所以的下去多做几次求解过程加深其求解原理。
KMP算法的实现:
- 这个和next很像,原理不好表达,具体就看代码(其实上面已经大概说完了整个匹配过程)。
int KMP(cont char *str, const char *ptr, int pose)//pose表示开始匹配位置
{
int len_str = strlen(str);
int len_ptr = strlen(ptr);
int i = pose;
int j = 0;
int *next = (int *)malloc(sizeof(int) * len_ptr);
Getnext(ptr, next, len_ptr);
while(i < len_str && j < len_ptr)
{
if(str[i] == ptr[j] || j == -1)
{
i++;
j++;
}
else
{
j = next[j];
}
}
if(j != len_ptr)
{
return -1;
}
return i - j;
}