KMP算法

KMP算法

    在朴素字符串匹配算法中如果某次匹配失败,则每次把字符串的指针回溯到最开头,这造成了效率低的后果。KMP算法是利用要匹配的模板字符串自身的信息来移动模板字符串的指针。

图1

    如图1所示,在ababa已经匹配的情况下,下一字符不匹配的时候,发现aba是ababa的真后缀同时也是其前缀,同时它也是最长的。由于有一部分后缀等于前缀了,所以不用将要匹配的模板字符的指针移至最起始位置。而要与模板字符匹配必须首先要与其前缀匹配,所以匹配的文本指针不需要移动,只需要继续对比后续的字符。
    设要匹配的模板字符为 P P P ,设前缀函数 π \pi π 求某一字符串其真后缀等于其前缀的最长长度。同时设 π ( q ) = m a x { k : k &lt; q 且 P k ⊐ P q } \pi(q) = max \lbrace k : k&lt;q 且 P_k \sqsupset P_q \rbrace π(q)=max{k:k<qPkPq} ,即求P的q长度前缀中的其真后缀等于其前缀的最长长度。
    求得了 π ( q ) \pi(q) π(q) 函数,可以按照自动机的模式,设状态为q,每次输入更新状态,当状态为模板长度时,就求得了满足的子串。

void KMP_MATCHER(char* T, char* P)
{
	int q = 0, i;

	for (i = 0; T[i]; i++)
	{
		while (q > 0 && P[q] != T[i])
			q = PI[q];

		if (P[q] == T[i])
			q++;

		if (q == P_LEN)
		{
			//do something
			q = PI[q];
		}
	}
}

    以上代码中,T为搜寻的文本字符串,P为模式字符串,PI为 π ( q ) \pi(q) π(q) 函数。P_LEN为模式P的长度,也是自动机的终态。算法为只要发生不匹配的情况,则移动模式字符串的指针,直到匹配或者已经移到模式字符串的首部了。匹配的话,则匹配的字符数加一,也即是后缀等于前缀的个数加一。
    现在的问题转变为如何快速的求解出 π ( q ) \pi(q) π(q) 函数。

计算 π ( q ) \pi(q) π(q) 函数

    在计算 π ( q ) \pi(q) π(q) 函数之前,先看几个引理,引理的详细证明可参考《算法导论》。


    设 π ∗ ( q ) = { π [ q ] , π ( 2 ) [ q ] , . . . , π ( t ) [ q ] } \pi^*(q)=\lbrace \pi[q], \pi^{(2)}[q],...,\pi^{(t)}[q]\rbrace π(q)={π[q],π(2)[q],...,π(t)[q]},其中 π ( i ) [ q ] = π [ π ( i − 1 ) [ q ] ] \pi^{(i)}[q]=\pi[\pi^{(i-1)}[q]] π(i)[q]=π[π(i1)[q]]。迭代至 π ( i ) [ q ] = 0 \pi^{(i)}[q] =0 π(i)[q]=0 时结束。
引理1 P P P 是长度为 m m m 的模式,其前缀函数为 π \pi π,对 q = 1 , 2 , . . . , m q=1,2,...,m q=1,2,...,m π ∗ [ q ] = { k : k &lt; q 且 P k ⊐ P q } \pi^*[q]=\lbrace k:k&lt;q 且 P_k \sqsupset P_q \rbrace π[q]={k:k<qPkPq}

    对这条引理可以这么理解,设 r = π ( q ) r=\pi(q) r=π(q) 按照定义 r r r π ∗ ( q ) \pi^*(q) π(q) 中最大的值,则次大的值只能在前缀为 r r r 长度,后缀为 r r r 长度里继续匹配,由于后缀已经和前缀相等了,前缀与后缀的匹配变成了与 P r P_r Pr 自身的匹配,变成了子问题,即 π [ r ] \pi[r] π[r] ,因为 π [ r ] = π [ π [ q ] ] \pi[r]=\pi[\pi[q]] π[r]=π[π[q]],以此类推,求得的就是 π ∗ [ q ] \pi^*[q] π[q]


引理2 P P P 是长度为 m m m 的模式,其前缀函数为 π \pi π,对 q = 1 , 2 , . . . , m q=1,2,...,m q=1,2,...,m,如果 π [ q ] &gt; 0 \pi[q]&gt;0 π[q]>0,则 π [ q ] − 1 ∈ π ∗ [ q − 1 ] \pi[q]-1 \in \pi^*[q-1] π[q]1π[q1]

    这条引理比较容易理解,就是匹配的前缀长度减少了一,因为原来前缀就和后缀相同,这时后缀范围减一也自然成立。
    这条引理想要说明的一点就是如果 r ∈ π ∗ [ q − 1 ] r \in \pi^*[q-1] rπ[q1] ,那么可能 π [ q ] = r + 1 \pi[q] = r+1 π[q]=r+1,注意这里仅是可能。我们可以首先把 π ∗ [ q − 1 ] \pi^*[q-1] π[q1] 中满足 π [ q ] \pi[q] π[q] 的子集列出来:定义子集 E q − 1 = { k ∈ π ∗ [ q − 1 ] : P [ k + 1 ] = P [ q ] } E_{q-1} =\lbrace k \in \pi^*[q-1]:P[k+1]=P[q] \rbrace Eq1={kπ[q1]:P[k+1]=P[q]}。那么其中 E q − 1 E_{q-1} Eq1 中的最大值加1即为 π [ q ] \pi[q] π[q]。也就是推论3


推论3 P P P 是长度为 m m m 的模式,其前缀函数为 π \pi π,对 q = 1 , 2 , . . . , m q=1,2,...,m q=1,2,...,m
π [ q ] = { 0 , 如 果 E q − 1 = ∅ 1 + m a x { k ∈ E q − 1 } , 如 果 E q − 1 ≠ ∅ \pi[q] = \begin{cases} 0,&amp;如果E_{q-1}=\emptyset \\ 1+max\lbrace k \in E_{q-1} \rbrace,&amp;如果E_{q-1} \ne \emptyset \end{cases} π[q]={0,1+max{kEq1},Eq1=Eq1̸=


     按照最后的推论3来求 π [ q ] \pi[q] π[q] ,再求 E q E_q Eq 。由于 π [ q ] &lt; q \pi[q] &lt; q π[q]<q ,所以先满足条件的肯定是最大的,从而求得 π [ q ] \pi[q] π[q]

void COMPUTE_PREFIX_FUNCTION(char* P)
{
	int q, k;
	P_LEN = strlen(P);

	PI[1] = 0;
	k = 0;
	for (q = 2; q <= P_LEN; q++)
	{
		while (q > 0 && P[k] != P[q-1])
			k = PI[k];

		if (P[k] == P[q - 1])
			k++;

		PI[q] = k;
	}
}

参考

算法导论

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值