KMP字符串匹配算法

字符串的模式匹配是以下的一种常见问题:给定字符串 s s s(主串)和 t t t(模式串) ( ∣ s ∣ = n , ∣ t ∣ = m , m ≤ n ) (|s|=n,|t|=m,m\leq n) (s=n,t=m,mn),求 t t t s s s中第一次出现的位置。如:

s = " a b a b c a b c a c b a b " s="ababcabcacbab" s="ababcabcacbab"
t =            " a b c a c " t=\ \ \ \ \ \ \ \ \ \ "abcac" t=          "abcac"
t t t首次出现在对齐处,即主串的第6位。很容易想到一种枚举的方法(Brute Force):

1.BF算法(Brute Force)

BF算法即从 i = 1 i=1 i=1开始, i i i至多到 n − m + 1 n-m+1 nm+1,每次将模式串 t t t的开始与 s s s的第 i i i位对齐并进行对照,即比较 s [ i . . . i + m − 1 ] s[i...i+m-1] s[i...i+m1] t [ 1... m ] t[1...m] t[1...m]是否相等。显然,这种算法的最差复杂度当 t t t每次都在最后一位与 s s s失配时出现,总比较次数 = m ( n − m + 1 ) = O ( n m ) =m(n-m+1)=O(nm) =m(nm+1)=O(nm).

2.KMP算法

KMP算法通过计算模式串 t t t的一些额外信息 ( n e x t (next (next数组 ) ) )来避免不必要的必定失配的情况,从而优化时间复杂度。它由Knuth、Morris和Pratt同时提出,故得名KMP算法。

现考虑BF算法在匹配失配时的情况。设 s s s t t t s [ i ] , t [ j ] s[i],t[j] s[i],t[j]处失配,即:
s [ i − j + 1... i − 1 ] = t [ 1.. j − 1 ] s[i-j+1...i-1]=t[1..j-1] s[ij+1...i1]=t[1..j1]
s [ i ] ≠ t [ j ] s[i]\neq t[j] s[i]=t[j]
按照BF算法,失配后应将 t t t右移一位,使得 s [ i − j + 2 ] 与 t [ 1 ] s[i-j+2]与t[1] s[ij+2]t[1]对齐进行下一次匹配。此时,若对于模式串 t t t

t [ 1... j − 2 ] ≠ t [ 2... j − 1 ]                ( 1 ) t[1...j-2] \neq t[2...j-1]\ \ \ \ \ \ \ \ \ \ \ \ \ \ (1) t[1...j2]=t[2...j1]              (1)

那么就有

t [ 1... j − 2 ] ≠ t [ 2... j − 1 ] = s [ i − j + 2... i − 1 ] t[1...j-2] \neq t[2...j-1] = s[i-j+2...i-1] t[1...j2]=t[2...j1]=s[ij+2...i1],

因此

t [ 1... j − 2 ] ≠ s [ i − j + 2... i − 1 ] t[1...j-2] \neq s[i-j+2...i-1] t[1...j2]=s[ij+2...i1](可参照下图,相同颜色表示串相同)
在这里插入图片描述

图中 s s s中橙色框一定与 t 2 t_2 t2(下一次匹配)中的绿框不相等(式 ( 1 ) (1) (1)),因此下一次匹配一定会在模式串的 1... j − 2 1...j-2 1...j2处失败 ( ∗ ) (*) ()
同理,当有 t [ 1... j − 3 ] ≠ t [ 3... j − 1 ] t[1...j-3] \neq t[3...j-1] t[1...j3]=t[3...j1]时,下下次匹配一定会失败。那么什么时候匹配有可能成功呢?我们需要找到从大到小第一个出现的值 k k k,使得 t [ 1... k ] ≠ t [ j − k . . . j − 1 ] t[1...k] \neq t[j-k...j-1] t[1...k]=t[jk...j1] t [ 1... k − 1 ] = t [ j − k + 1... j − 1 ] t[1...k-1]=t[j-k+1...j-1] t[1...k1]=t[jk+1...j1],即子串 t [ 1... j − 1 ] t[1...j-1] t[1...j1]的最长的相同的前后缀长度+1。

如: t = " a b a a b c a c " t="abaabcac" t="abaabcac"
j = 6 j=6 j=6时, t [ 1...5 ] t[1...5] t[1...5]最长相同的前后缀为 " a b " "ab" "ab"长为 2 2 2,因此此时 k k k值为 3 3 3.

当我们能求出这个 k k k的值后,我们就可以推断出下一次可能匹配成功的位置,见下图。
在这里插入图片描述
我们已经断定跳过的几次匹配一定会失配,因此我们可以直接将 t [ 1 ] t[1] t[1] s [ i − k + 1 ] s[i-k+1] s[ik+1]对齐,而由相同前后缀的性质,标红框的部分完全相同,因此还可以跳过这一次匹配的 1... k − 1 1...k-1 1...k1部分,直接将 s [ i ] s[i] s[i] t [ k ] t[k] t[k]进行比对。

通过上面的分析,我们可以总结出 k k k的一些性质:
( 1 ) k (1)k (1)k只与模式串 t t t自己有关,而且是一个关于下标 j j j的函数,即 k = n e x t ( j ) ; k=next(j); k=next(j);
( 2 ) 0 ≤ n e x t ( j ) ≤ j − 1 (2)0 \leq next(j) \leq j-1 (2)0next(j)j1,它表明 t [ 1... j ] t[1...j] t[1...j]有长为 n e x t ( j ) − 1 next(j)-1 next(j)1的相同前后缀 ; ; ;
( 3 ) (3) (3)当不存在相同前后缀时, n e x t ( j ) = 1 ; next(j)=1; next(j)=1;
( 4 ) (4) (4)定义 n e x t ( 1 ) = 0. next(1)=0. next(1)=0.

3.next数组的求法

n e x t next next数组可通过归纳求得。以下是具体分析:
( 1 ) n e x t [ 1 ] = 0 ; (1)next[1]=0; (1)next[1]=0;
( 2 ) (2) (2)假设 n e x t [ 1 ] , n e x t [ 2 ] , . . . , n e x t [ j ] next[1],next[2],...,next[j] next[1],next[2],...,next[j]均已知,现求出 n e x t [ j + 1 ] . next[j+1]. next[j+1].
n e x t [ j ] = k , next[j]=k, next[j]=k,这说明 t [ 1... j − 1 ] t[1...j-1] t[1...j1]有长为 k − 1 k-1 k1的相同前后缀。

①当 t [ k ] = t [ j ] t[k]=t[j] t[k]=t[j],那么 t [ 1... j ] t[1...j] t[1...j]便有长为 k k k的相同前后缀。那么 t [ 1... j ] t[1...j] t[1...j]能否有比 k k k更长的相同前后缀呢?答案是不能。如图:
在这里插入图片描述
红框圈起来的部分代表 n e x t ( j ) next(j) next(j)所表示的相同前后缀;紫色框表示 t [ k ] = t [ j ] t[k]=t[j] t[k]=t[j]所带来的 t [ 1... j ] t[1...j] t[1...j]的长为 k k k的前后缀。左边的紫框一定不等于绿框,否则 n e x t ( j ) next(j) next(j)便可取到 k + 1 , k+1, k+1,因此两个黄框一定不相等,因为它们前 k k k位一个是紫框,一个是绿框。因此当 t [ k ] = t [ j ] , n e x t ( j + 1 ) = k + 1 = n e x t ( j ) + 1 t[k]=t[j],next(j+1)=k+1=next(j)+1 t[k]=t[j],next(j+1)=k+1=next(j)+1.

②当 t [ k ] ≠ t [ j ] t[k] \neq t[j] t[k]=t[j],此时视为模式串 t t t与自身匹配,而且 t [ k ] t[k] t[k] t [ j ] t[j] t[j]失配。由①可知,只能寻找比 k k k更短的相同前后缀。在这里插入图片描述

此时将下面的串不断向右移动,判断重叠部分(即绿框)是否相等,找出最长的相同前后缀。由KMP ( ∗ ) (*) ()部分可知,可以直接将下面的串的第 k ′ = n e x t [ k ] k'=next[k] k=next[k]与上面串的第 j j j位比较(否则一定会失配)。如果移动后 t [ j ] = t [ k ′ ] , t[j]=t[k'], t[j]=t[k],那么便有 n e x t ( j + 1 ) = k ′ + 1 = n e x t ( k ) + 1 = n e x t ( n e x t ( j ) ) + 1 ; next(j+1)=k'+1=next(k)+1=next(next(j))+1; next(j+1)=k+1=next(k)+1=next(next(j))+1;如若不然,不断令 k ( n ) = n e x t ( k ( n − 1 ) ) k^{(n)}=next(k^{(n-1)}) k(n)=next(k(n1)),直到:
( a ) (a) (a) t [ j ] = t [ k ( n ) ] t[j]=t[k^{(n)}] t[j]=t[k(n)],则 n e x t ( j + 1 ) = k ( n ) + 1. next(j+1)=k^{(n)}+1. next(j+1)=k(n)+1.
( b ) (b) (b)直至迭代到 k ( n ) = 1 k^{(n)}=1 k(n)=1时, t [ j ] ≠ t [ k ( n ) ] t[j] \neq t[k^{(n)}] t[j]=t[k(n)],即上下串仅一位重叠仍不相等(下图),说明没有公共前后缀,此时令 n e x t ( j + 1 ) = 1. next(j+1)=1. next(j+1)=1.
在这里插入图片描述

4.代码实现

-求 n e x t next next数组

void GetNext(char *t,int *next)
{
	int i = 1,j = 0;
	while(i<m)//m为模式串长度
	{
		if(j == 0 || t[i] == t[j])
		{
			i++;
			j++;
			next[i] = j;
		}
		else j = next[j];
	}
}

代码中 i i i表示当前要求的 n e x t next next下标, j j j表示下面的串的第 j j j位与上面的串(上一部分图解)尝试进行匹配。
j = = 0 j == 0 j==0表示当迭代到最后仍然没有公共前后缀的情况,此时 n e x t [ + + i ] = 1. next[++i]=1. next[++i]=1.
t [ i ] = t [ j ] t[i]=t[j] t[i]=t[j]说明当前匹配成功,记录下 n e x t [ + + i ] = j + 1 next[++i]=j+1 next[++i]=j+1,否则迭代 j j j n e x t [ j ] 。 next[j]。 next[j]

-KMP

int KMP(char *s,char *t)
{
	int i = 1,j = 1;
	while(i<=n && j<=m)
	{
		if(j == 0 || s[i] == t[j])
		{
			i++;
			j++;
		}
		else j = next[j];
	}
	if(j>m) return i-m;
	else return 0;
}

与求 n e x t next next数组很相似,当 j = 0 j=0 j=0时,说明 s [ i ] s[i] s[i] t [ 1 ] t[1] t[1]不相等,此时模式串右移一位将 j j j置为 1 1 1,同时 i + + i++ i++使得模式串从头开始与主串下一位匹配。
s [ i ] = = t [ j ] s[i]==t[j] s[i]==t[j]时,说明当前位匹配成功, i , j i,j i,j均自增。否则 j j j迭代为 n e x t [ j ] next[j] next[j].
j > m j>m j>m时,说明模式串匹配完成,匹配成功位置为 ( i − 1 ) − m + 1 = i − m (i-1)-m+1=i-m (i1)m+1=im(由于 i i i在匹配成功时自增了 1 1 1,需要减掉)

KMP算法的复杂度为 O ( n + m ) = O ( n ) . O(n+m)=O(n). O(n+m)=O(n).

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值