KMP--算法,分析及递归实现

吹下牛先

额, 因为在准备考研,基本上不博客了,重新遇到了KMP算法,心血来潮,所以就来捣鼓一下了,没错,我来装 X 的 ,我的麒麟臂在早已饥渴难耐了。

KMP算法

想必来看KMP算法的人,都知道KMP算法是拿来干嘛的,实现字符串模式匹配的高效算法,不过拿去其他模式匹配应该也可以,只要你想得到。现在再重述一遍算法思想:

假设已有: 目标串 T[0…n-1],模式串 P[0…m-1] ,注:(T[i…j] 理解成 t i t i + 1 . . . t j t_{i}t_{i+1}...t_{j} titi+1...tj, $ 0 \lt i \lt n-1$),P也如此理解。
不失一般性,从 T 中的第 s ( 0 ≤ s ≤ n − 1 0 \le s \le n-1 0sn1 ) 个位置与 P 中 第 j ( $ 1 \le j \le m-1$ ) 个位置匹配失败。 则此时有 T[s…s+j-1] = P[0…j-1],按照朴素算法,下一步是将从 t s + 1 t_{s+1} ts+1 开始与 P 比较,若匹配成功将有 T[s+1…s+m] = P[0…m-1],也即有 T[s+1…s+j-1] = P[0…j-2]。
综合,已有条件:

  1. T[s…s+j-1] = P[0…j-1] → t s t s + 1 . . t s + j − 1 = p 0 p 1 . . p j − 1 \to t_{s}t_{s+1}..t_{s+j-1} = p_{0}p_{1}..p_{j-1} tsts+1..ts+j1=p0p1..pj1
  2. T[s+1…s+j-1] = P[0…j-2] → t s + 1 t s + 2 . . t s + j − 1 = p 0 p 1 . . p j − 2 \to t_{s+1}t_{s+2}..t_{s+j-1} = p_{0}p_{1}..p_{j-2} ts+1ts+2..ts+j1=p0p1..pj2

联合1、2可得, p 0 p 1 . . p j − 2 = p 1 . . p j − 1 p_{0}p_{1}..p_{j-2} = p_{1}..p_{j-1} p0p1..pj2=p1..pj1,即P[0…j-2] = P[1…j-1]

上面的推导表明,只有当 P[0…j-2] = P[1…j-1] 成立时,才会有 T[s+1…s+m] = P[0…m-1]。 如果 P[0…j-2] $ \ne$ P[1…j-1] ,必然导致 T[s+1…s+m] $ \ne$ P[0…m-1]。

所以自然就不用进行T从s+1开始的模式匹配,而是尝试从s+2开始,如果等式不成立, 就继续尝试s+3开始, 直到第k 次, 首次使得:
∗ ∗ 认 住 这 家 伙 ∗ ∗ P [ 0.. k ] = P [ j − k + 1.. j − 1 ] **认住这家伙**P[0..k] = P[j-k+1..j-1] P[0..k]=P[jk+1..j1]
成立。
这个时候观察 P[j-k+1…j-1] 有:

T[s+j-k+1…s+j-1] = P[j-k+1…j-1] = P[0…k] → \to T[s+j-k+1…s+j-1] = P[0…k] — 公式 3

因为之前是在第 j 个匹配失败的,然后又确认了上的 3 式的成立,所以我们很很愉快的从 p k + 1 p_{k+1} pk+1 s s + j s_{s+j} ss+j 开始比较了。后面的比较也是不断的重复上述的步骤。可以发现比较过的T是不再回溯的,所以效率很高。 下面将以Next(j) 表示第j个匹配失败后,返回的k值,即下一次要与 T[s+j] 比较的 P[k], 这里明确一点, 返回的k值, 必需满足 P[0…k] = P[j-k+1…j-1] 。

接着,我们将问题转换到了找 k 值,假设已经知道 Next(j) = k,现在要求 Next(j+1) 。
对于Next(j) = k ,明确知道 k 代表的是P[ j ] 与T[s+j]匹配失败后,下一个要与 T[s+j] 匹配的位置, 至于P[ k ] 与 T[ s+j ] 是否相等,是不知道的,根据 k 值,我们有:
P[j-k…j-1] = P[0…k-1] = T[s+j-k…s+j-1]

现在,假设在第 j+1 次匹配失败, 则有 P[0…j] = T[s…s+j] → \to P[j-k…j] = T[s-k…s+j] 。
此时分两种情况:
(1) P[k] = P[j]
观察 P[0…k-1] 和 P[j-k…j] ,已有 P[j-k…j-1] = P[0…k-1] ,若 P[k] = P[j] 则有 P[0…k] = P[j-k…j] ,即我们找到了第 j+1 次匹配失败时,应该返回下一个与 T[s+j+1] 比较的位置, Next(j+1) = k+1 。

(2) P[k] != P[j]
但是我们有 P[j-k…j-1] = P[0…k-1] , 问题类比成 P[k] 与 P[j] 匹配失败, 则需要在找到一个 h = Next(k) ,使得 P[0…h-1] = P[k-h…k-1] ,再比较 P[h] 与 P[j] ,若相等,返回 h+1,此时有P[h+1] 与 T[s+j+1] 进行匹配。若P[h] != P[j] ,继续重复步骤(2),直至传入 Next() 的参数为0时,返回 -1 表示失败,即P[0…j-1] 中没有一个等于 P[j] 的,此时 s := s+1 ,目标串向前推进一步。

下面将根据上述分析过程,写出递归算法计算第j个位置匹配失败后应该返回的k值。

算法将以两种形式给出,第一种是伪代码,第二种是C++代码。

伪代码:

Next(P, j)
	if j == 0
		return -1
	k = Next(P, j-1)
	while k != -1
		if P[k] = P[j-1]
			return k+1
		k = Next(P, k)
	return k+1
			
		

C++代码:

//教材代码
Next(int next[])
{
	int j = 0, k = -1, lengthP = curLength;
	next[0] = -1;
	while( j < lengthP) //Pj < Pn
	{
		if( k == -1 || ch[j] == ch[k] ) // 实质为P[j-1] 与 P[k] 的比较
		{
			++j; ++k;
			next[j] = k; //最终转变为 P[j] = P[k+1]
		}
		else k = next[k];	//P[j-1] != P[k] , 往P[0..k-1] 中找到与 P[j-1]相等的位置
	}
}

int Next(string P, int j)
{
    if( j == 0 ) return -1;
    int k = j - 1;

    do
    {
        k = Next(P, k);
        if( P[k] == P[j-1] ) return k+1;
    }while( k != -1 );

    return k + 1;
}

//配上一个KMP, 返回的是1..n形式位置。嗯...没有在怎么测试过。
int KMP(string T, string P)
{
    int s, j ;
    s = j = 0;

    while(  abs(j) < P.length() && s < T.length() )
    {
        cout<<s<<","<<j<<endl;
        if( j == -1 || T[s] == P[j] )
        {
            ++s; ++j;
        }
        else
            j = Next(P, j);
    }
    if( j == P.length() ) return s - P.length() + 1;
    return -1;
}

总结

递归算法虽然效率低,每次都要重新计算一个 Next 值,但是更加的直观,让我们了解KMP是如何实现的,有时间+有兴趣就将递归改写成非递归,并用数组将结果存起来,当然你可以用一个小技巧,调用Next()一次,结果存一起,也是可以的拉。一开始,还以为能够很好的 Control 住。最后…希望能有点帮助把…还有参考了 殷人昆主编的《数据结构C++版》。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值