KMP算法

KMP算法的引入 

     KMP算法就能很好地解决冗余问题。

     其主要思想为:

          在失配后,并不简单地从目标串下一个字符开始新一轮的检测,而是依据在检测之前得到的有用信息(稍后详述),直接跳过不必要的检测,从而达到一个较高的检测效率。

      如            

          ab c d e a b c d e a b c d f

              || :     | | :

              cd f     c d f

         第一次失配   新的检测

         当第一次失配后,并不从红色标记字符'd'开始检测,而是通过一些有用信息,直接跳过后几个肯定不可能匹配的冗余字符,而直接让模式串Pattern从目标串的红色标记字符'c'开始新一轮的检测,从而达到了减少循环次数的效果

  

KMP算法思想详述与实现 

        前面提到,KMP算法通过一个“有用信息”可以知道目标串中下一个字符是否有必要被检测,这个“有用信息”就是用所谓的“前缀函数(一般数据结构书中的next函数)”来存储的。

        这个函数能够反映出现失配情况时,系统应该跳过多少无用字符(也即模式串应该向右滑动多长距离)而进行下一次检测,在上例中,这个距离为4.

        总的来讲,KMP算法有2个难点:

              一是这个前缀函数的求法。

              二是在得到前缀函数之后,怎么运用这个函数所反映的有效信息避免不必要的检测。

下面分为两个板块分别详述:

 

前缀函数的引入及实现

 

前缀函数的引入 

        对于前缀函数,先要理解前缀是什么:

        简单地说,如字符串A ="abcde"        B ="ab"

        那么就称字符串B为A的前缀,记为B ⊏ A(注意那不是"包含于",Bill把它读作B前缀于A),说句题外话——"⊏"这个符号很形象嘛,封了口的这面相当于头,在头前面的就是前缀了。

        同理可知 C ="e","de" 等都是 A 的后缀,以为C ⊐ A(Bill把它读作C后缀于A)

 理解了什么是前、后缀,就来看看什么是前缀函数:

         在这里不打算引用过多的理论来说明,直接引入实例会比较容易理解,看如下示例:

  

      (下述字符若带下标,则对应于图中画圈字符)

      这里模式串 P =“ababaca”,在匹配了 q=5 个字符后失配,因此,下一步就是要考虑将P向右移多少位进行新的一轮匹配检测。传统模式中,直接将P右移1位,也就是将P的首字符'a'去和目标串的'b'字符进行检测,这明显是多余的。通过我们肉眼的观察,可以很简单的知道应该将模式串P右移到下图'a3'处再开始新一轮的检测,直接跳过肯定不匹配的字符'b',那么我们“肉眼”观察的这一结果怎么把它用语言表示出来呢?

  
     我们的观察过程是这样的:

          P的前缀"ab"中'a' != 'b',又因该前缀已经匹配了T中对应的"ab",因此,该前缀的字符'a1'肯定不会和T中对应的字串"ab"中的'b'匹配,也就是将P向右滑动一个位移是无意义的。

          接下来考察P的前缀"aba",发现该前缀自身的前缀'a1'与自身后缀'a2'相等,"a1 ba2" 已经匹配了T中的"a b a3",因此有 'a2' == 'a3', 故得到 'a1' == 'a3'......

          利用此思想,可推知在已经匹配 q=5 个字符的情况下,将P向右移 当且仅当 2个位移时,才能满足既没有冗余(如把'a'去和'b'比较),又不会丢失(如把'a1' 直接与 'a4' 开始比较,则丢失了与'a3'的比较)。

          而前缀函数就是这样一种函数,它决定了q与位移的一一对应关系,通过它就可以间接地求得位移s。

  

     通过对各种模式串进行上述分析(大家可以自己多写几个模式串出来自己分析理解),发现给定一个匹配字符数 q ,则唯一对应一个有效位移,如上述q=5,则对应位移为2.

     这就形成了一一对应关系,而这种唯一的关系就是由前缀函数决定的。

     这到底是怎样的一种关系呢?

     通过对诸多模式串实例的研究,我们会找到一个规律(规律的证明及引理详见《算法导论(第二版)》)。

     上例中,P 已经匹配的字符串为"ababa",那么这个字符串中,满足既是自身真后缀(即不等于自身的后缀),又是自身最长前缀的字符串为"aba",我们设这个特殊字串的长度为L,显然,L = 3. 故我们要求的 s = q - L = 5 - 3 = 2 ,满足前述分析。

    

     根据这个规律,即可得到我们要求的有效位移s,等于已经匹配的字符数 q 减去长度 L。

     即 s = q - L

     因为这个长度 L 与 q 一一对应,决定于q,因此用一函数来表达这一关系非常恰当,这就是所谓的前缀函数了。

     因为已经分析得到该关系为一一对应关系,因此用数组来表示该函数是比较恰当的,以数组的下标表示已经匹配的字符数 q,以下标对应的数据存储 L。

 

// LIS.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include<iostream>
#define N 100  
using namespace std;

void BuildNext(char * pattern, int *next)
{
	int lolp; //lolp 表示lenth of longest prefix
	int nocm; //number of characters matched
	int pattern_len=0;//lenth of the pattern
	next[0] = -1;
	while ('\0' != pattern[pattern_len])
		pattern_len++;
	for (nocm = 1; nocm < pattern_len; nocm++)
	{
		lolp = next[nocm - 1];
		while (lolp>0 && pattern[lolp + 1] != pattern[nocm])
		{
			lolp = next[lolp];
		}
		if (pattern[nocm] == pattern[lolp + 1])
		{
			next[nocm] = lolp + 1;
		}
		else
		{
			next[nocm] = -1;
		}
	}
}

int KMP(char * target, char * pattern, int * next)
{
	int t=0, p=0;
	int t_len = 0;
	int p_len = 0;
	while ('\0' != target[t_len])
		t_len ++;
	while ('\0' != pattern[p_len])
		p_len++;
	while (p < p_len && t < t_len)
	{
		if (pattern[p] == target[t])
		{
			p++;
			t++;
		}
		else
		{
			if (p == 0)
			{
				t++;
			}
			else
			{
				p = next[p - 1] + 1;
			}
		}
	}
	return (p == p_len) ? (t - p_len) : -1;
}

int main()  
{  
    char target[ N ] = {0};  
    char pattern[ N ] = {0};  
    int slen, plen;  
    int next[ N ];  
  
   
	cin >> target;
	cin >> pattern;
    
    BuildNext( pattern, next);  
    printf( "%d\n", KMP( target, pattern, next ) );  
	system("PAUSE");
    return 0;  
}  


更详细的一个过程:http://www.matrix67.com/blog/archives/115

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值