KMP算法是一个广泛应用于字符串查找与匹配的算法,特点就是计算速度快,例如在m长度的字符串中查找匹配长度为n的字符串,他的时间复杂度可以是m+n
对于字符串的查找与匹配,要是我们没有学习过数据结构或者是算法,我们也能够想出来一个很传统的方法:那就是在主z串中一个一个字符推进,然后和要匹配的字符串一位一位字符比较。虽然这个方法也能完成任务,但是在复杂的的长字符串中,计算花费时间特别长,时间复杂度可以到m*n字符
传统暴力匹配算法:
int index(char *a,char *b) {
int lena = strlen(a);
int lenb = strlen(b);
while(i < lena && j < lenb) {
if(a[i] == b[j]) {
++i;
++j;
}
else {
j=1;
i=++k;
}
}
if(j >= lenb) return k;
else return 0;//查找失败返回0
}
//在字符串a中查找b
为了优化计算,我们可以学习使用KMP算法来帮助我们解决查找匹配这一类的问题。
要理解KMP算法,我们首先要知道最大前后缀字符串的概念,下面图就对最大前后缀做的一个解释:用字符串 ABCADABCD 举例
由此我们可以引入一个next数组,来记录我们搜索到的最大前后缀,还是用上图的字符串为例我们可以最后一个字符的位置当做next的下标,最大前后缀为此时next的值,于是我们可以写出next(ABCADABCD):
next[0]=-1
next[1]=0
next[3]=0
next[6]=1
next[7]=2
next[8]=3
接下来,我们我们可以试着看看最大前后缀在匹配中的运用,我们在ABAABCADBABCADABCDBAD
中寻找
ABCADABCD
这就是一次KMP算法查找匹配的操作,代码的具体实现是这样的。第一步,我们要先计算next,然后是按照思路去匹配
//在字符串a中查找字符串b
int KMP(char *a, char *b) {
int lena = strlen(a);
int lenb = strlen(b);
int now = 0, k = -1;
next[0] = -1;
while(now < lenb) {
if(k == -1 || b[now] == b[k]) {
now++;
k++;
next[now] = k;
}
else k = next[k];
//这个位置的回溯是一个类似递归的思想,我们不是一步就回到字符串的开头,而是如果现有的最大前后缀内还有最大前后缀,可以回溯到这个位置继续判断,是一个优化的思想
}
//先求b串的next[]
int i = 0, j = 0;
while(i < lena && j < lenb) {
if(j == -1 || a[i] == b[j]) {
i++;
j++;
}
else j = next[j];
}
if(j >= lenb) return (i - lenb);//找到了就返回匹配到的起点位置
else return (-1);//没到到返回-1
}
但是这个依旧不是最优的,我们在算next的时候,没有考虑连续重复字符串的情况,例如在AAACBAAAB中寻找AAAB这个字符串的时候
void nextval(char *s, int nextval[]) {
int len = strlen(s);
int now = 0, k = -1;
nextval[0] = -1;
while(now < len) {
if(k == -1 || s[now] == s[k]) {
now++;
k++;
if(s[now] != s[j]) nextval[now] = k;//这个位置多了一个判断
else nextval[now] = nextval[k];
}
else k = nextval[k];
}
}
总结:
KMP算法是一个很巧妙的算法,利用前后缀的思想,跳过了繁琐的步骤。同时仅仅通过一个字符串长度的空间,却换来了极大的速度优化,这也是一个用空间换时间的典型案例。