KMP字符串模式匹配详解

个人觉得这篇文章是网上的介绍有关KMP算法更让人容易理解的文章了,确实说得很“详细”,耐心地把它看完肯定会有所收获的~~,另外有关模式函数值next[i]确实有很多版本啊,在另外一些面向对象的算法描述书中也有失效函数 f(j)的说法,其实是一个意思,即next[j]=f(j-1)+1,不过还是next[j]这种表示法好理解啊:

                                          KMP字符串模式匹配详解

KMP 字符串模式匹配通俗点说就是一种在一个字符串中定位另一个串的高效算法。简单匹配算法的时间复杂度为 O(m*n);KMP 匹配算法。可以证明它的时间复杂度为 O(m+n).
.简单匹配算法
先来看一个简单匹配算法的函数:
int Index_BF ( char S [ ], char T [ ], int pos )
{
/* 若串 S 中从第 pos(S 的下标 0 pos<StrLength(S)) 个字符
起存在和串 T 相同的子串,则称匹配成功,返回第一个
这样的子串在串 S 中的下标,否则返回 -1    */
int i = pos, j = 0;
while ( S[i+j] != '\0'&& T[j] != '\0')
if ( S[i+j] == T[j] )
j ++; // 继续比较后一字符
else
{
i ++; j = 0; // 重新开始新的一轮匹配
}
if ( T[j] == '\0')
return i; // 匹配成功    返回下标
else
return -1; // S ( pos 个字符起 ) 不存在和串 T 相同的子串
} // Index_BF
    此算法的思想是直截了当的:将主串 S 中某个位置 i 起始的子串和模式串 T 相比较。即从 j=0 起比较 S[i+j] T[j] ,若相等,则在主串 S 中存在以 i 为起始位置匹配成功的可能性,继续往后比较 ( j 逐步增 1 ) ,直至与 T 串中最后一个字符相等为止,否则改从 S 串的下一个字符起重新开始进行下一轮的 " 匹配 " ,即将串 T 向后滑动一位,即 i 1 ,而 j 退回至 0 ,重新开始新一轮的匹配。
例如:在串 S= ”abcabcabdabba” 中查找 T=” abcabd” (我们可以假设从下标 0 开始) : 先是比较 S[0] T[0] 是否相等,然后比较 S[1] T[1] 是否相等 我们发现一直比较到 S[5] T[5] 才不等。如图:
 
当这样一个失配发生时, T 下标必须回溯到开始, S 下标回溯的长度与 T 相同,然后 S 下标增 1, 然后再次比较。如图:
这次立刻发生了失配, T 下标又回溯到开始, S 下标增 1, 然后再次比较。如图:
这次立刻发生了失配, T 下标又回溯到开始, S 下标增 1, 然后再次比较。如图:


又一次发生了失配,所以 T 下标又回溯到开始, S 下标增 1, 然后再次比较。这次 T 中的所有字符都和 S 中相应的字符匹配了。函数返回 T S 中的起始下标 3 。如图:

. KMP匹配算法
还是相同的例子,在 S= ”abcabcabdabba” 中查找 T =”abcabd” ,如果使用 KMP 匹配算法,当第一次搜索到 S[5] T[5] 不等后, S 下标不是回溯到 1 T 下标也不是回溯到开始,而是根据 T T[5]==’d’ 的模式函数值( next[5]=2 ,为什么?后面讲),直接比较 S[5] T[2] 是否相等,因为相等, S T 的下标同时增加 ; 因为又相等, S T 的下标又同时增加。。。最终在 S 中找到了 T 。如图:



KMP 匹配算法和简单匹配算法效率比较,一个极端的例子是:
S= AAAAAA…AAB (100 A) 中查找 T=”AAAAAAAAAB”, 简单匹配算法每次都是比较到 T 的结尾,发现字符不同,然后 T 的下标回溯到开始, S 的下标也要回溯相同长度后增 1 ,继续比较。如果使用 KMP 匹配算法,就不必回溯 .
对于一般文稿中串的匹配,简单匹配算法的时间复杂度可降为 O (m+n) ,因此在多数的实际应用场合下被应用。
KMP 算法的核心思想是利用已经得到的部分匹配信息来进行后面的匹配过程。看前面的例子。为什么 T[5]==’d’ 的模式函数值等于 2 next[5]=2 ),其实这个 2 表示 T[5]==’d’ 的前面有 2 个字符和开始的两个字符相同,且 T[5]==’d’ 不等于开始的两个字符之后的第三个字符( T[2]=’c’ . 如图:
也就是说,如果开始的两个字符之后的第三个字符也为 ’d’, 那么,尽管 T[5]==’d’ 的前面有 2 个字符和开始的两个字符相同, T[5]==’d’ 的模式函数值也不为 2 ,而是为 0
   前面我说:在 S= ”abcabcabdabba” 中查找 T =”abcabd” ,如果使用 KMP 匹配算法,当第一次搜索到 S[5] T[5] 不等后, S 下标不是回溯到 1 T 下标也不是回溯到开始,而是根据 T T[5]==’d’ 的模式函数值,直接比较 S[5] T[2] 是否相等。。。为什么可以这样?
刚才我又说:“( next[5]=2 ),其实这个 2 表示 T[5]==’d’ 的前面有 2 个字符和开始的两个字符相同”。请看图   :因为, S[4] ==T[4] S[3] ==T[3] ,根据 next[5]=2 ,有 T[3]==T[0] T[4] ==T[1] ,所以 S[3]==T[0] S[4] ==T[1] (两对相当于间接比较过了),因此,接下来比较 S[5] T[2] 是否相等。。。
有人可能会问: S[3] T[0] S[4] T[1] 是根据 next[5]=2 间接比较相等,那 S[1] T[0] S[2] T[0] 之间又是怎么跳过,可以不比较呢?因为 S[0]=T[0] S[1]=T[1] S[2]=T[2] ,而 T[0] != T[1], T[1] != T[2],==> S[0] != S[1],S[1] != S[2], 所以 S[1] != T[0],S[2] != T[0].  还是从理论上间接比较了。
有人疑问又来了,你分析的是不是特殊轻况啊。
假设 S 不变,在 S 中搜索 T= abaabd ”呢?答:这种情况,当比较到 S[2] T[2] 时,发现不等,就去看 next[2] 的值, next[2]=-1 ,意思是 S[2] 已经和 T[0] 间接比较过了,不相等,接下来去比较 S[3] T[0] 吧。
假设 S 不变,在 S 中搜索 T= abbabd ”呢?答:这种情况当比较到 S[2] T[2] 时,发现不等,就去看 next[2] 的值, next[2]=0 ,意思是 S[2] 已经和 T[2] 比较过了,不相等,接下来去比较 S[2] T[0] 吧。
假设 S=” abaabcabdabba S 中搜索 T= abaabd ”呢?答:这种情况当比较到 S[5] T[5] 时,发现不等,就去看 next[5] 的值, next[5]=2 ,意思是前面的比较过了,其中, S[5] 的前面有两个字符和 T 的开始两个相等,接下来去比较 S[5] T[2] 吧。
总之,有了串的 next 值,一切搞定。那么,怎么求串的模式函数值 next[n] 呢?(本文中 next 值、模式函数值、模式值是一个意思。)
. 怎么求串的模式值next[n]
定义
1 next[0]= -1  意义:任何串的第一个字符的模式值规定为 -1
2 next[j]= -1   意义:模式串 T 中下标为 j 的字符,如果与首字符
相同,且 j 的前面的 1—k 个字符与开头的 1—k
个字符不等(或者相等但 T[k]==T[j] )( 1 k<j )。
如: T=”abCabCad” next[6]=-1 ,因 T[3]=T[6]
3 next[j]=k    意义:模式串 T 中下标为 j 的字符,如果 j 的前面 k
字符与开头的 k 个字符相等,且 T[j] != T[k] 1 k<j )。
                       T[0]T[1]T[2] 。。。 T[k-1]==
T[j-k]T[j-k+1]T[j-k+2]…T[j-1]
T[j] != T[k]. 1 k<j ;
(4) next[j]=0   意义:除( 1 )( 2 )( 3 )的其他情况。
 
举例
01 T= abcac ”的模式函数的值。
     next[0]= -1  根据( 1
     next[1]=0   根据 (4)   因( 3 )有 1<=k<j; 不能说, j=1,T[j-1]==T[0]
     next[2]=0   根据 (4)   因( 3 )有 1<=k<j; T[0]=a != T[1]=b
     next[3]= -1  根据 (2)
     next[4]=1   根据 (3) T[0]=T[3] T[1]=T[4]
       
下标
0
1
2
3
4
T
a
b
c
a
c
next
-1
0
0
-1
1
T= abcab ”将是这样:
下标
0
1
2
3
4
T
a
b
c
a
b
next
-1
0
0
-1
0
为什么 T[0]==T[3], 还会有 next[4]=0 , 因为 T[1]==T[4], 根据 (3)” T[j] != T[k]” 被划入( 4 )。
02 )来个复杂点的,求 T=”ababcaabc” 的模式函数的值。
next[0]= -1    根据( 1
         next[1]=0    根据 (4)
         next[2]=-1   根据 (2)
next[3]=0   根据 (3) T[0]=T[2] T[1]=T[3] 被划入( 4
next[4]=2   根据 (3) T[0]T[1]=T[2]T[3] T[2] !=T[4]
next[5]=-1  根据 (2) 
next[6]=1   根据 (3) T[0]=T[5] T[1]!=T[6]
next[7]=0   根据 (3) T[0]=T[6] T[1]=T[7] 被划入( 4
next[8]=2   根据 (3) T[0]T[1]=T[6]T[7] T[2] !=T[8]
 
下标
0
1
2
3
4
5
6
7
8
T
a
b
a
b
c
a
a
b
c
next
-1
0
-1
0
2
-1
1
0
2
只要理解了 next[3]=0 ,而不是 =1 next[6]=1 ,而不是 = -1 next[8]=2 ,而不是 = 0 ,其他的好象都容易理解。
03)    来个特殊的,求 T=”abCabCad” 的模式函数的值。
下标
0
1
2
3
4
5
6
7
T
a
b
C
a
b
C
a
d
next
-1
0
0
-1
0
0
-1
4
         
next[5]= 0  根据 (3) T[0]T[1]=T[3]T[4], T[2]==T[5]
next[6]= -1  根据 (2) 虽前面有 abC=abC, T[3]==T[6]
next[7]=4   根据 (3) 前面有 abCa=abCa, T[4]!=T[7]
 
如果你觉得有点懂了,那么
练习:求 T=”AAAAAAAAAAB” 的模式函数值,并用后面的求模式函数值函数验证。
意义
 next 函数值究竟是什么含义,前面说过一些,这里总结。
设在字符串 S 中查找模式串 T ,若 S[m]!=T[n], 那么,取 T[n] 的模式函数值 next[n],
1.        next[n]= -1 表示 S[m] T[0] 间接比较过了,不相等,下一次比较 S[m+1] T[0]
2.        next[n]=0 表示比较过程中产生了不相等,下一次比较 S[m] T[0]
3.        next[n]= k >0 k<n, 表示 ,S[m] 的前 k 个字符与 T 中的开始 k 个字符已经间接比较相等了,下一次比较 S[m] T[k] 相等吗?
4.        其他值,不可能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值