KMP算法,也就是字符串匹配算法,是很多场合都会用到的算法,这个算法想要解决什么样的问题呢?举例来说,有一个字符串"BBC ABCDAB ABCDABCDABDE",我想知道,里面是否包含另一个字符串"ABCDABD"?
传统的求解,我想很多人都已经想到了,遍历源字符串的每一位嘛,依次和要匹配的字符串比较,code很easy,但效率较为低下,可以想到,时间复杂度为O(N*M),那有没有更好的算法解决这类问题呢?
KMP算法就是能以更快的效率去解决这类问题,它的时间复杂度能降低到O(N+M),是不是觉得很厉害? 确实很厉害,以至于很多人对这个算法都理解不了,本文将尽量用最浅显的语言,来试图将KMP讲述清楚。
就像我最开始说的,我们马上能想到的解决方式就是遍历源字符串的每一位,依次和匹配字符串比较,那么相对于这种方法,KMP究竟快在哪里呢? 其实整个KMP优化的核心就是 当匹配失败时,我是直接i++移动到下一位继续匹配,还是移动到我通过某种算法得出来的位置?(这个位置是最优解) 这是什么意思呢? 举个例子。
对于这样的一种情况:
当匹配失败时,我该从下一个index B开始匹配呢,还是 可以通过验证,中间的某一段位置都可以确定匹配不了目的字符串,而从后面的某个位置开始匹配呢。
传统的做法:
KMP的做法:
KMP就是通过这样的一种思路,来加速整个匹配过程的,于是整个核心 就变成了,当匹配失败时该如何找到下次对应的匹配位置? 在KMP算法里,是通过next数组来保存这一信息的。因此下面我们就来讲一讲如何求出这个next数组。
提到next数组,就不得不要了解两个概念:"前缀"和"后缀"。 "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。而next数组保存的就是最长前缀和后缀相等的长度值。
先不管怎么求的next数组,假设我们已经有了next数组,在kmp中是如何通过next数组,来实现匹配的加速呢?下面直接上代码,对着code理解:
int kmp_code(string str1,string str2)
{
int len1=str1.length();
int len2=str2.length();
int i=0,j=0;
//字符串为空或目标字符串长度大于源串。
if((str1 == NULL)||(str2 == NULL)||(len1<len2))
return -1;
int* next = GetNextArray(str2);
//循环条件,index值小于各自str长度
while(i<len1 && j<len2)
{
//字符匹配,则各自++
if(str1[i] == str2[j])
{
i++;
j++;
}
else if(next[j] == -1) //没有前缀后缀相等的值。
i++;
else //从next数组里得到index值
j=next[j];
}
return j == len2? i-j: -1; //返回值
}
不管next怎么求的,代码是不是非常的简洁?,下一步就要讲解如何求next数组了,即最长的前缀与后缀相等的值,这里我们规定:next[0]=-1;next[1]=0;
int* GetNextArray(string str)
{
int *next =new int[str.length()];
if(str.length() == 1)
return next[0] = -1;
next[0] =-1;
next[1] =0;
int index = 2 //next下标
int pos = 0; // 对应的next值
while(index < str.length())
{
if(str[index-1] == ms[pos]) //字符相等,则next值+1
next[index++] = ++pos;
else if(pos >0)
pos =next[pos]; //推到上一个next值,
else
next[pos++] = 0; //往前推的next值到0都找不到,则当前next值为0;
}
return next;
}
求next数组,可以说是整个KMP的核心了,如何快速求出next数组呢? 其实整个代码的思想,就是利用前面已经求好的next数组,来求当前index下的next数组。整个思路如下:
1.判断当前index字符与前者(记录好的)next数组对应的下标值(pos)的字符相等?,相等则pos值+1,为当前index的next值
2.不等,则往前推pos对应的next值,继续上述比较。
3.如果推到底,即 next值为0,则将当前index下的next值记录为0;
可以说,kmp的每一步都设计的非常巧妙,利用next数组,来加速匹配的过程,而next数组的求法也不是简单的暴力遍历解决,而是利用前面已经求好的next数组,来求解后面的next数组,可以说是相当的巧妙了。如今的计算机中,字符串匹配是无时无刻都在做的一件事,可以说,kmp是一个非常常用,也非常重要的一个算法。
注:本文讲解的比较浅显,因为kmp算法,确实不好理解,也不是一下就能理解会的东西,需要大家慢慢消化,而将kmp能说清楚,也不是一件容易的事情。代码虽短,却无时无刻体现着人类智慧的结晶