• KMP详解


    KMP详解

            既然你已经找到这儿了,说明你已经多多少少了解了一点儿KMP,至少已经听闻KMP匹配很快。本文不做严格的证明,只是帮助你理解KMP,以免像我一样,学了之后,不久就又忘了。

    KMP为什么比较快?像这样:


     

    当比较到ed的时候,没有必要从i=1和j=0开始,直接变成这种情况:

     

    因为之前的比较已经知道e前的abd前的ab一样,而第二串(也就是模式串T,第一串称为目标串S)的开始也是ab,所以c之前的字符和e之前的字符一样。所以j可以直接从d跳回到c,拿ec比较,显然也是不相同的的,之后的过程和这个相同。

    可以看到i从来都没有后退过(至于为什么i可以不回到1进行匹配,请参见算法书的相关章节),所以查找的时间就是目标串S的长度n。在查找之前需要预处理模式串T,时间是模式串的长度m(后面会说到),所以模式匹配的时间复杂度是O(n+m)。而以前的复杂度是O(n*m),所以快了不少。

     

    这个算法需要一个额外的数据,就是next[m]数组,是通过分析模式串T得到的匹配不成功时的跳转信息,next[j]表示下标为j的字符与目标串不匹配时,需要跳到下标next[j]处。next[0]=-1。

     

    匹配

    通过上面的分析,可以得到如下匹配代码:

    int find(char *s1,char *s2,int len1,int len2)
    {
    	int i,j;
    	i=j=0;
    	while(i<len1&&j<len2)
    	{
    		if(j==-1 || s1[i]==s2[j])
    		{
    			++i;
    			++j;
    		}else
    		{
    			j=next[j];
    		}
    	}
    	if(j==len2)
    	{
    		return i-len2;
    	}else
    	{
    		return -1;
    	}
    }

    s1是目标串,s2是模式串,len1是s1的长度,len2是s2的长度。

    当s1[i]==s2[j]的时候,++i;++j很好理解。

    但是当j==-1的时候怎么回事儿呢?-1是通过j=next[j]得到的,即s1[i]与s2[0]不相等的时候,j=next[0];(next[0]=-1上面有说到)这样j就变成-1了。就是说s1[i]一直匹配不成功,连s2[0]都没有匹配成功。所以就不再拿s1[i]匹配了,++i表示从下一个开始匹配。++j,刚好j==0。所以j==-1的时候,也需要++i;++j。

    这就是在s1中寻找s2的过程。简单吧。

     

    预处理

    下面来说说预处理s2的过程,也就是求next数组。

    回顾最前面的查找过程的讲解

     

    之所以可以从d跳回到c,是因为他们之前都有ab,所以next[5]=2。那个求next数组的过程,就是在当前字符之前找一个串是模式串的前缀。在模式串中匹配它的前缀,这本身就是模式匹配,所以求next数组的代码和前面的匹配代码基本上一样。(这也就可以理解,预处理的复杂度是O(m) )

    void getnext(char *s)
    {
    	int i=0,j=-1;
    	next[0]=-1;
    	while(s[i])
    	{
    		if(j==-1 || s[i]==s[j])
    		{
    			++i;
    			++j;
    			next[i]=j;
    		}else
    		{
    			j=next[j];
    		}
    	}
    }

    当存在一个以s[i]结尾的串和串s[0]…s[j]相同的时候(也就是代码中的s[i]==s[j],s[i-1]==s[j-1]等判断在之前的循环中已判断过),那么,就可以从j+1跳回i+1,所以有++i; ++j;next[i]=j;至于为什么j==-1也可以,参照之前对j==-1的分析。

    现在再说说,为什么j从-1开始。记得在匹配的代码中,i和j都是从0开始的。在开始的时候,i=0,想要找一个以s[i=0]结尾的串和模式串的前缀相同,这是不存在的,只有一个s[0],找不到两个串(注意,这两个串不能从相同的地方开始),-1就表示匹配失败。i=0的时候,匹配一定是失败的,所以j一定是-1。

    现在,基础的KMP已经讲完了。来个例子:

     

    当第二个b匹配失败的时候,因为他之前的a和前缀a一样,所以可以跳回到第一个b,下标为1。当d匹配失败的时候,他之前的ab和前缀ab一样,所以可以跳回到c,下标为2。

     

    next数组的改进

    继续分析上面的例子。

    当第二个b匹配失败的时候,因为他之前的a和前缀a一样,所以可以跳回到第一个b,下标为1。此时,还是字符b,显然,还是匹配失败,然后再跳到下标为0的位置。可以看出来,当跳转到的字符和当前字符一样的时候,需要继续跳转。改进就是让跳转一步到位。代码如下:

    void getnext2(char *s)
    {
    	int i=0,j=-1;
    	next[0]=-1;
    	while(s[i])
    	{
    		if(j==-1 || s[i]==s[j])
    		{
    			++i;
    			++j;
    			if(s[i]!=s[j])
    			{
    				next[i]=j;
    			}else
    			{
    				next[i]=next[j];
    			}
    		}else
    		{
    			j=next[j];
    		}
    	}
    }

    看,当s[i]==s[j]的时候,next[i]=next[j];i不是跳转到j,而是跳转到j应该跳转到的地方。

    新的next数组是:

     

    至于为什么之前的next数组只有一个-1,而新的next数组有两个,自己思考下吧。还有,之前求next数组的方法,对任意字符串来说是否只有一个-1,为什么?

     

     

     

    完整代码:

    #include<stdio.h>
    #include<string.h>
    int next[100];
    void getnext(char *s)
    {
    	int i=0,j=-1;
    	next[0]=-1;
    	while(s[i])
    	{
    		if(j==-1 || s[i]==s[j])
    		{
    			++i;
    			++j;
    			next[i]=j;
    		}else
    		{
    			j=next[j];
    		}
    	}
    }
    
    void getnext2(char *s)
    {
    	int i=0,j=-1;
    	next[0]=-1;
    	while(s[i])
    	{
    		if(j==-1 || s[i]==s[j])
    		{
    			++i;
    			++j;
    			if(s[i]!=s[j])
    			{
    				next[i]=j;
    			}else
    			{
    				next[i]=next[j];
    			}
    		}else
    		{
    			j=next[j];
    		}
    	}
    }
    int find(char *s1,char *s2,int len1,int len2)
    {
    	int i,j;
    	i=j=0;
    	while(i<len1&&j<len2)
    	{
    		if(j==-1 || s1[i]==s2[j])
    		{
    			++i;
    			++j;
    		}else
    		{
    			j=next[j];
    		}
    	}
    	if(j==len2)
    	{
    		return i-len2;
    	}else
    	{
    		return -1;
    	}
    }
    int main()
    {
    	int i,j,len1,len2;
    	char s1[100],s2[100];
    	while(gets(s1))
    	{
    		gets(s2);
    		len1=strlen(s1);
    		len2=strlen(s2);
    		getnext2(s2);
    		printf("
    模式串:");puts(s2);
    		printf("next[]:");
    		for(i=0;i<len2;++i)
    		{
    			if(next[i]==-1)
    			{
    				printf("&");
    				continue;
    			}
    			printf("%d",next[i]);
    		}
    		printf("
    &表示-1
    
    ");
    		int from=find(s1,s2,len1,len2);
    		if(from!=-1)
    		{
    			printf("Yes. From %d to %d
    ",from,from+len2-1);
    		}else
    		{
    			printf("No
    ");
    		}
    	}
    	return 0;
    }



  • 相关阅读:
    加入创业公司有什么利弊
    Find Minimum in Rotated Sorted Array II
    Search in Rotated Sorted Array II
    Search in Rotated Sorted Array
    Find Minimum in Rotated Sorted Array
    Remove Duplicates from Sorted Array
    Spiral Matrix
    Spiral Matrix II
    Symmetric Tree
    Rotate Image
  • 原文地址:https://www.cnblogs.com/riskyer/p/3318150.html
Copyright © 2020-2023  润新知