《大话数据结构》--KMP算法

传统的字符串匹配

 如上图所示,如果我们进行人工匹配的话,是不是2、3、4、5步骤都是多余的呢?那我们该如何消灭这种没必要的步骤呢?

KMP算法(KMP分别对应三个研究者的名字首字母)

再来看两个例子:

例子1:

S ababcaaa

T abac

匹配时,在第四位发生不匹配的情况,而前缀a(t1)和后缀a(t3)已经同S进行比较,且这俩相等,所以就没必要再让前缀a和S3再进行比较,而直接让T2和S4进行比较即可。(在这假定S和T的下标均从1开始)

例子2:

S ababcaaa

T ababb

匹配时,在第五位发生不匹配的情况,由于前缀ab(t12)和后缀ab(t34)相同,所以前缀(t12)无需在与t34对应的s34进行比较,故下一次匹配时,t3直接与s5进行比较即可。(在这假定S和T的下标均从1开始)

在朴素模式匹配算法中,每一次发生失配时都重新开始匹配,(i指针在S串上移动,j指针在T串上移动),所以i会回溯,但是经过分析可以发现,这种回溯是没有必要的。既然i值不回溯,那么变化的就只能是j值了。而经过分析,j值的变化与主串其实没什么关系,关键就取决于T串的结构中是否有重复的问题。然后又经过分析得到规律,j值取决于当前字符之前的串的前后缀的相似度。(这些相同的比过了,我就可以直接抄作业,一会儿就不用再比了~)。

故有以下函数定义:【参照书籍《大话数据结构》P138】,后面又经由分析得到,如果前后缀1个字符相等,则k值为2(在当前位置发生不匹配的时候,j变为2,因为第一个刚刚比过了是一样的),如果前后缀2个字符相等,则k值为3......依此类推。

next[j]表示,在j位置发生失配时,j的值变为next[j]。(在j位置发生失配时,回到next[j]重新开始匹配)

                   0,当j=1时 //第一个就发生失配的话,那回到开始之前,下一次重新开始

next[j]     = Max{k|1<k<j,且"p1...pk-1"=="pj-k+1...pj-1"},当此集合不为空时 //找前后缀中有多少个                                                                                                                           字符相等,1->2...

                  1,其他情况 //在第二个发生失配时,或者前后缀中没有相同的字符

//通过计算返回字串T的next数组
void get_next(String T,int* next)
{
    int i,j;//i用来指示后缀,j用来指示前缀
    i=1;
    j=0;
    next[1]=0;
   	while(i<T[0])//此处T[0]表示串的长度
    {
        if(j==0||T[i]==T[j])//T[i]表示后缀的单个字符,T[j]表示前缀的单个字符
        {					//如果j==0,则next[i]=1,
            ++i;			//譬如说:一开始,i==1,j==0时,计算next[2]时,next[2]==1
            ++j;
            next[i]=j;		
        }
        else
            j=next[j];//若字符不相同,则j值回溯。
        			  //譬如说:接下来比较i==2,j==1时,如果不相等的话,那j值要变回到0,为了一会儿利用if写上,next[3]=1。因为在j==3处发生不匹配且第一个字符和第二个字符不一样,那就不能抄作业,一会儿又要从1处开始匹配,所以借助next[1]的值为0回到if。
    }
}

以两个例子阐述上述算法执行过程:

 

 

接下来我们再思考一个问题。

next[]很简便了吗?那我们来看一个新的字符串T="aaaaax"与字符串S="aaaabcde"进行匹配,如果按照我们之前所说的,当在T[5]处发生失配的话,按照之前"前缀和后缀中有1个相同的K==2,“2 3” ",那这里应该是4,所以下一次有T[4]和S[5]进行匹配。但是在现实中,由于T[5]的a和S[5]的b不相等,而T[1、2、3、4]的a又和T[5]的a相等,那么也就是说T[1、2、3、4]肯定和S[5]不匹配了,那就没有必要再接着比较了。也就是说,由于T串的第二、三、四、五位置的字符都与首位的“a”相等,那么可以用首位next[1]的值去取代与它相等的字符后续next[j]的值。所以可以在得到next的基础上再接着判断,如果和前面的某个字符相等,那就直接把该字符对应的next[j]赋给它就好了。但是在算法实现中,我们将这两个过程同时进行。

//求模式串T的next函数修正值并存入数组nextval
void get_nextval(String T,int* nextval)
{
	int i,j;				//i指示后缀,j指示前缀
	i=1;
	j=0;
	nextval[1]=0;
	while(i<T[0])
	{
		if(j==0||T[i]=T[j])
		{
			++i;
			++j;
			if(T[i]!=T[j])
				nextval[i]=j;//跟之前的求next一样
			else //如果相等的话,把j的next[j]也给i一份
				nextval[i]=nextval[j];
		}
		else
			j=nextval[j];
	}
}

最后,搜索过程:

//返回子串T在主串S中第pos个字符后的位置,若不存在,则函数返回值为0
int Index_KMP(String S,String T,int pos)
{
    int i=pos;//i用于主串S当前位置下标,如果pos不为1,则从pos位置开始匹配
    int j=1;//j用于定义字串T中当前位置下标值
    int nextval[255];//定义一个nextval数组
    get_Nextval(T,nexval);
    while(i<=S[0]&&j<=S[0])//限定范围
    {
        if(j==0||S[i]==T[j])
        {
            ++i;
            ++j;
        }
        else
        {
            j=nextval[j];
        }
    }
    if(j>T[0])	//说明匹配完了,
        return i-T[0];  //S=ababcde T=aba 在j==3,i==3时成功匹配,然后++i,++j,那就是j=4,跳        出循环,此时j>T[0],然后要返回子串T在主串S中第pos个字符后的位置(包括第pos个字符),所以就是当前下标减去子串长度,就可以得到子串首字符对应的S串中的字符的pos值。
    else
        return 0;
}

最后一定要注意的一点是:KMP算法仅当S与T之间存在许多"部分匹配"的情况下才体现出它的优势,否则它与朴素的字符串匹配模式差异并不明显。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值