这里就不对KMP算法进行介绍了,直接给上我的理解、思路和代码。
-
设定:
i:主串的伪指针
j:模式串的伪指针
前缀:模式串中靠前的某一段
后缀:伪指针 i 左侧部分靠后的某一段
同步测试:主串第 i 位字符与模式串第 j 位字符进行比较并判断是否相等
同步成功:同步测试结果相等
失去同步:同步测试结果不相等 -
两个串进行同步测试,当失去同步时,让 j 前移直至模式串前缀与主串后缀重合,而 i 不动。
-
移动位数 = 模式串已匹配的字符数 - 失去同步前最长前缀匹配字符数
-
如此移动可以保证忽略尽量多的无用同步过程,并同时保证不丢失应该做同步测试的所有可能位置。
-
我的理解:失去同步时,若直接让 i 回溯到主串与模式串首个同步成功的字符之后,这样很浪费时间;若让 i 保持不动,而 j 回到初始位置,两串重新进行同步测试,则两个串前面相同的部分又要重新测试一次(若有相同部分的话),如此也浪费了一定的时间甚至可能错过该进行同步测试的部分而导致错误。而KMP的算法可以判断主串前缀与模式串后缀是否有相同的部分,并记录下来,当失去同步时,让 j 回溯到到可以使前缀与后缀重合的位置即可,如此便节省了无用比较(重合部分已经知道相等,不必再比较)。
举个例子,主串:abcdabcdabe,模式串:abcdabe,匹配到主串第7位时失去同步。
若按第一种算法,i 会回溯到第2位(b),j会回溯到初始位置(a),如此很浪费时间;
若按第二种算法,i 会保持不动(c),j会回溯到初始位置(a),然后主串第7位于模式串第1位重新开始同步测试,如此一来主串中应进行同步测试的第5、6位被错过,导致错误;
若按KMP算法,失去同步时,i 前面的部分,后缀ab,与模式串前缀ab相同,此时让 j 回溯到前缀之后(c),而 i 保持不动(c),如此不仅没有错过第5、6位的ab的同步测试,也节省了5、6位进行同步测试的时间。 -
KMP算法本体代码如下:
int str_index_KMP(sq_string &S,sq_string &T)
{
if(S.length<T.length)
{
return -1;
}
int i=0,j=0;
vector<int> next(T.length,-1);
GetNext(next,T);
while(i!=S.length)
{
if(j==-1 || S.str[i]==T.str[j])
{
++i;
++j;
if(j==T.length)
{
return (i-T.length);
}
}
else
{
j=next[j];
}
}
return -1;
}
-
需要一个辅助数组next来表示在模式串中,伪指针 j 移动到每一个下标时其前面部分是否有相同的前缀与后缀。
-
初始伪指针在第0位时,前面部分没有内容,则next数组对应位置值设为-1;伪指针在第1位时,其前面只有一个数字,肯定不存在相同的前缀与后缀,则next数组对应位置值设为0;当伪指针的左侧部分不存在相同的前缀与后缀时,next对应位置的值设为0,否则设为前缀与后缀的最大相同长度。
-
获取next数组的代码如下:
void GetNext(vector<int> &next,sq_string &T)
{
int n=next.size();
int i=0,j=-1;
while(i<n)
{
if(j==-1 || T.str[i]==T.str[j])
{
++i;
++j;
next[i]=j;
}
else
{
j=next[j];
}
}
}