KMP算法(未完结)

来源:互联网 发布:安卓新闻app源码 编辑:程序博客网 时间:2024/04/28 07:16

问题重现:
已知P=sasu,S=sssaaasususasus,要求找出t中包含s的个数?


分析:
现在假设前面已经有m个字符匹配成功,但是第m+1个字符却匹配失效,这时候就需要找到一个更可能的位置开始下一次搜索匹配。

如模式字符串sasu
假设已经匹配了其sas,最后一个a没匹配上,如*sasb*(*代表任意多个任意字符,下面的|表示从何处重新开始寻找)
这是如果从*s|asb*开始下一次匹配的话,也就是依次逐个匹配的方式被称为朴素匹配算法。
而如果通过事先的计算,发现只要从*sa|sb*开始匹配的话,明显就加快了比较的速度。

这种算法本质上就是模式字符串自身匹配的过程。
同样对于上面的字符串sasx进行以下模拟,并用next[i]表示匹配了i个后执行下一步查找可直接从当前位置到最可能匹配处的位数:
如果只匹配上了s,下一步*s|xxx*,发现下一个未知值并不重要,也就是next[1]=1
如果只匹配上了sa,下一步*sa|xxx*,最后的s!=a,根据next[1],可以直接从1开始找,不包含s*,也就是next[2]=2
如果只匹配上了sas,下一步*sa|sxxx*,最后的s==s,根据next[2],可以直接从2开始找,发现包含了s,也就是next[2]=2
如果只匹配上了sasu,下一步*sasu|xx*,最后的sa!=su,根据next[3],可以直接从2开始找,不包含s*,也就是next[2]=4。完全匹配,即成功找到一个。

综述以上:发现其实就是在匹配上的字符串s*中从第1个(0起编号)位置开始寻找s*子串出现的位置,并且s*子串出现的位置只可能从前一个结果的位置才可能出现。
关键就是在寻找满足P0P1..Pk==Pj-kPj-k+1...Pj的k值,换句话说前k个字符与j个字符之前的k个字符相同。


优化:
上面的算法其实还是可以做新的改进,用二维数组记录下s*子串的长度,这样下次就可以直接跳过更多的位数进行快速匹配了。


KMP算法
kmp思想的核心:next[j]的求法
动态规划:

现在我们用

子问题状态定义:next[j]记录失配时模式串应该用哪一个字符与S串进行比较
           -1        当j=0时,-1假定的,标志量
next[j] =  max{k | 0<=k<j 且 P0P1..Pk-1=Pj-kPj-k+1...Pj-1}
           0         其他情况,从P串首开始

P0P1..Pk-1也就是前缀真子串,Pj-kPj-k+1...Pj-1即真子后缀串
前缀真子串==后缀真子串,换句话说前k个字符与j个字符之前的k个字符相同

现假设next[j]=k
①:Pk=Pj时,即P0P1...Pk=Pj-kPj-k+1...Pj, 则:
            next[j+1]=k+1。
   又因为next[j]=k,则
            next[j+1]=next[j]+1。
   
②:Pk!=Pj时,即P0P1...Pk!=Pj-kPj-k+1...Pj【这一步可以使用依次轮询的求法】。这里又将模式串的匹配问题转化为了上面我们提到的”主串“和”模式串“中寻找next的问题,你可以理解成在模式串的前缀串和后缀串中寻找next[j]的问题。现在我们的思路就是一定要找到这个k2,使得Pk2=Pj,然后将k2代入①就可以了。
设   k2=next[k]   则有P0P1...Pk2-1=Pj-k2Pj-k2+1...Pj-1。
若   Pj=Pk2,     则 next[j+1]=k2+1=next[k]+1。
若   Pj!=Pk2,     则可以继续像上面递归的使用next,直到不存在k2为止。

 


结合上面的分析,其实可以发现这是一个动态规划的问题:
求next函数的过程是一个递推的过程:
1.首先由定义得next[0]=-1,next[1]=0;
2.假设已知next[j]=k,又T[j] = T[k],则显然有next[j+1]=k+1;
3.如果T[j]!= T[k],则令k=next[k],直至T[j]等于T[k]为止。

原创粉丝点击