数据结构与算法

KMP详解

在讲解KMP算法之前,还是有必要回顾一下BF(简单粗暴但效率相对较低)算法吧

其中S是主串,T是模式串,pos是从什么位置开始匹配

int Inedex(String S,String T,int pos){
int i=pos; //需注意一点无论是主串还是模式串,我们S[0]和T[0]都保存的是字符串长度,而不是字符串本身的字符
int j=1;
while(i<=S[0]&&j<=T[0]){ /*若i小于S的长度且j小于T的长度时循环*/
if(S[i]==T[j]){
i++;
j++;
}else{
i=i-j+2; //这里之所以是i-j+2是因为T[0]不是字符,保存的是长度,你用两个这样的数组比较一下就可以了
}
}
if(j>T[0])
return i-T[0];//找到了,就返回匹配成功的字串的第一个位置。
else
return 0;
}

相信大家看了代码,就知道它的工作原理了吧,即在主串中一个一个与模式串匹配,如果失配,主串的位置就+1,模式串就回到1,这样又重复执行前面的比较,这样的匹配简单易懂,但缺乏技巧。

什么是KMP算法呢?

KMP算法是三位前辈(D.E.Knuth、J.H.Morris、V.R.Pratt)发表的,他们真实太厉害了,向前辈致敬!!!!!

具体表现
第一种情况

S12Ilovedafanzi
i0123456789101112
T5Il0vx
j012345

明显T中第五个与S中第五个失配了,但你仔细看也会发现,模式串中这个五个不同,所以我们完全可以下一次比较用S中的e和模式串中的I比较。而不必用BF那么累赘。

第二种情况

S11www.dafanzi
i01234567891011
T3ww.
j0123

明显的T中的第三个和S中的第三个失配了,但这次我们也可以看出下一次匹配中,S中的失配位置(坐标为3的)应该和T串中坐标为2的W开始匹配。

第三种情况

S12bbsbbsbbcdfz
i0123456789101112
T6bbsbbc
j0123456

明显S中的6个位置和T中第6个位置失配了,然而我们也机智的发现S中失配的那个S应该和T中的第三个位置开始匹配了。

第四种情况

S11sssssssdfzi
i01234567891011
T6ssssbc
j0123456

(先声明一下这个表位置摆放没摆好,这里的T表的坐标5个应该对齐S中的坐标8)
可以看出S中的第8个位置和T中的第5个位置失配了,然而我们仔细观察,发现下一次比较其实可以让S中的第8个位置和T中的第4个位置开始匹配了

观察了这么多,其实可以说出KMP的算法的用途了,就是一次匹配失败后让主串失配的那个字符串和模式串中第几个字符和它比较。而不用让主串回溯了,大大提高了效率。而且,其实我们可以总结一些通俗的规律呢?,你比如说第一种情况中,模式串中第5个字符没有被匹配到,而它前面的没有相等的前缀和后缀,所以主串中的失配字符下一次就和模式串中的第一个元素比较。而第二种情况,因为模式串失配的那个字符前有一个前缀和后缀相等,所以呢,主串的失配字符下一次就和模式串的第二个元素比较。第三种情况,前缀和后缀有两个字符,所以主串失配的字符下一次就和模式串的第三个元素开始比较。第四种情况,前缀后后缀有3个元素相等,所以主串失配的字符下次就和模式串的第4个比较。
也许有人问了,第四个怎么就是前缀和后缀相等有4个元素呢?记住:这里的前缀和后缀相等元素的个数是以字符相等情况下,尽可能多的个数,自要前缀和后缀不完全相同就可以了

规律一般化

在这里我们引入next数组,这在KMP算法里是核心,我们就用第三种情况做分析吧。

S12bbsbbsbbcdfz
i0123456789101112
T6bbsbbc
j0123456
next012123

next数组的功能就是决定模式串中某一个字符失配了,相应下一次失配的那个主串字符就和模式串中的第几个字符匹配(这个模式串的下标就是next数组的值)。这里需要额外说的是,我们默认如果模式串中第一个字符失配,其next[1]是0,这样的意思就是,下次从主串的下一个位置开始匹配了,你画个图,就知道了。

next数组代码原理


void get_next(String T,int *next)
{
int i,j;
i=0;
j=1;
next[1]=0
while(j<T[0])
{
if(i==0||[T[i]==T[j])
{
++i;
++j;
next[j]=i;
}else
i=next[i];
}
}

z这一组代码就实现了NEXT数组了

KMP算法改进

先给出一个例子吧:

S11aaaadafanzi
i01234567891011
T3aaaaax
j0123456
j012345

按照正常的KMP的话,我们会按照给的next数组进行匹配,但是发现d与他们前面四个的匹配都是无效的, 因为d和a匹配失败了,那么d还有必要和前面的a去做比较吗?所以我么可以直接让d和next[1]做比较。这样可以得出改进后的代码

void get_nextval(String T,int *nextval)
{
int i,j;
int i=0;
int j=1
next[1]=0;
while(j<T[0])
{
if(i==0 || T[i]==T[j])
{
i++;
j++;
if(T[i]==T[j])
nextval[j]=nextval[i];
else
nextval[j]=i
}else{
i=T[i];
}
}
}

强烈建议按照上面说的数组,画一次图,推算一次。
然后整体的匹配方法 ,

int Index_KMP1(String S, String T, int pos)
{
int i = pos;
int j = 1;
int next[255];
get_nextval(T, next);
while (i <= S[0] && j <= T[0])
if (j==0 || S[i] == T[j])
++i;
++j;
}
else
j = next[j];
if (j > T[0])
return i-T[0];
else
return 0;
}

因为调用get_nextval(T, next)函数,一个循环为m(m就是模式串的长度),然后while循环执行了n次(n为主串的长度),所以时间复杂度就是m+n吧
转载请注明源地址http://blog.csdn.net/xiaofanzidafanzi/

我们这里说的KMP不是拿来放电影的(虽然我很喜欢这个软件),而是一种算法KMP算法是拿来处理字符串匹配的。换句话说,给你两个字符串,你需要回答,B串是否是A串的子串(A串是否包含B串)。比如,字符串A="I'm matrix67",字符串B="matrix",我们就说B是A的子串。你可以委婉地问你的MM:“假如你要向你喜欢的人表白的话,我的名字是你的告白语的子串吗?” 解决这类问题,通常我们的方法是枚举从A串的什么位置起开始与B匹配,然后验证是否匹配。假如A串长度为n,B串长度为m,那么这种方法的复杂度是O (mn)的。虽然很多时候复杂度达不到mn(验证时只看头一两个字母就发现不匹配了),但我们有许多“最坏情况”,比如,A= "aaaaaaaaaaaaaaaaaaaaaaaaaab",B="aaaaaaaab"。我们将介绍的是一种最坏情况下O(n)的算法(这里假设 m<=n),即传说KMP算法。 之所以叫做KMP,是因为这个算法是由Knuth、Morris、Pratt三个提出来的,取了这三个人的名字的头一个字母。这时,或许你突然明白了AVL 树为什么叫AVL,或者Bellman-Ford为什么间是一杠不是一个点。有时一个东西有七八个人研究过,那怎么命名呢?通常这个东西干脆就不用人名字命名了,免得发生争议,比如“3x+1问题”。扯远了。 个人认为KMP是最没有必要讲的东西,因为这个东西网上能找到很多资料。但网上的讲法基本上都涉及到“移动(shift)”、“Next函数”等概念,这非常容易产生误解(至少一年半前我看这些资料学习KMP时就没搞清楚)。在这里,我换一种方法来解释KMP算法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值