- 字符串中寻找字符串,可能最容易先想到的是---暴力匹配算法。比如:
这张图中的主串与子串进行比对,一个字一个字的比对,当主串第四个B与子串第四个A不匹配时,主串就会跳过第一个字符,到第二个字符重新匹配,直到与子串的四个字符都匹配上,才算完成。这个算法原理很简单,理解起来也不难,但最大的问题就是它的时间复杂度。时间复杂度是一个o(n*m)的算法,n和m代表:主串和子串的长度。如果用暴力算法的话,效率极低,但我们可以用KMP算法,这样就很简单了。
- 那什么时KMP算法呢?通俗的来讲:KMP算法是一种改进的字符串匹配算法,(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少子串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)。
- 我们就在想,子串中最后一个数不等于主串中的第四个数,我们可不可以把子串往后移呢。这样就总能找到与主串相等的数了。那,怎样才能找到跳过多少个字符呢?如何找到呢?这里就需要用到KMP中的定义数组next了。以一个图为例:对于next的数组,子串中最后一个数匹配失败后,它所对应的数为2,则子串就跳过2个字符,然后再看到主串与子串有无匹配。这样就不需要回退主串中的指针了。只需要子串跳过n个字符,与之匹配就可以了。是不是比之前的暴力算法简单了很多。
- 首先,要解决的问题就是Next数组的的生成。next的本质:就是寻找子串中“相同前后缀的长度”,并且一定时最长的前后缀。子串中第一个A,显然不存在比他还要短的前后缀,(第二个也一样)所以为0。第三个也为A,则为1,第四个也为A则为2。如果子串还有很多串,则以此类推。我们可以用for循环暴力来求,但效率比较低,我们可以用递推的方式来求解。
则代码为:
def build_next(patt)
{
//这里要计算next数组
next = [0];//初始化,初识元素为1
prefix_len = 0;
i = 1;
while (i < len(patt))
if (patt[prefix_len] == patt[i])
prefix_len += 1;
next.append(prefix_len)
i += 1;
else
if (prefix_len == 0)
next.append(0);
i += 1;
else
prefix_len = next[prefix - 1]
return next;
}
这样next就能很好的表示出来啦。
- 那next数组已经实现了,我们就可以来实现KMP算法的程序实现了。则代码为
这里就是KMP算法的的程序实现,但是还没有完,还需要主函数的帮衬,才能完成这项算法,这一步就比较简单了,则代码为:def kmp_search(string, patt) { next = build_next(patt);//next算法已经算出 i = 0;//主串中的指针 j = 0;//子串中的指针 while (i < len(string)) if (string[i] == patt[j])//字符匹配,指针后移 i += 1; j += 1; else if (j > 0)//字符失配,根据next跳过子串前面的一些字符 j = next[j - 1]; //子串第一个字符就失配 else i += 1; if (j == len(patt))//匹配成功 return i - j; }
int main() { char arr1[] = "ABABAA"; char arr2[] = "ABAA"; def(arr1, arr2); next(arr1, arr2); kmp_search(arr1, arr2); int build_next(arr1, arr2); //函数声明 return 0; }
这里KMP算法基本上已经写完了,不得不说,next数组的算法时最难的,需要理解主串与子串的走向,以及它的变化规律。还要避免重复的运算。
-
可能KMP算法比较难以理解,但总之,只要知道其中的原理,就能很好的写出算法。你学会了吗?如果我有写的不对的地方,请你们指出来哦。我会虚心接受的。