KMP(字符串查找)(hihocoder)

题目来源:hihocode-KMP

思路:

         KMP算法,在hihocoder也有讲解,这里说一下我的理解。

         一般字符串匹配查找时,我们一般会使用这种方法

    //以i开头,开始和模式串进行匹配(s为原串,p为模式串) 
	for(int i=0,j=0;i<s.length();++i){
	    while(i<slen && j<plen && s[i] == p[j])i++,j++;
            i = i - j;            
            if(j == p.length())return i;
        }

      这种算法的最坏时间复杂度是 原串的长度成以模式串的长度 即 s.length()*p.length() ;

      但这种方法是可以改进的,这种方法在进行匹配的时候是有很多重复的匹配操作的;

      例如有原串 babababcbab 模式串 bababb
          在第一次匹配时,从s[0]开始,如图,会在s[5]不匹配 

                                《KMP(字符串查找)(hihocoder)》      《KMP(字符串查找)(hihocoder)》 

           然后进行第二次匹配,从s[1] 开始,如图,会在s[5]不匹配

                                 《KMP(字符串查找)(hihocoder)》

             在这两次中,第二次的匹配是从 s[1]开始,而s[1]显然和p[0]不相等,这实际上这可以由第一次的匹配得到的;

             可见这种方法的冗余在某些情况下可能会更大。

         故而有了KMP算法。

         如果在匹配不成功后,我可以不将 i 改变,而是保持 i ,继续和 p 某一位置 k 进行匹配,这样算法不就成了线性的了吗?

          假设这个位置记为next[j],也就是说,如果匹配不成功 i 不变,而 j 变为 next[j];

          那么这个next[j]怎么求呢?

          实际上保持 i 不变 ,而使 i 继续和 p 的某一位置 k 继续匹配,

          当且仅当 在 p.substring(0,k) == p.substring(k+1,j-1),如果有多个当然去k最大的那一个;   (p.substring(i,j) 表示以i开始,j结尾的子串)

          下面讲具体的做法

                     next[j]表示当 s[i] != p[j] 时 j 应该向左移动到那里;

                    显然 next[0] = -1 表示 s[i] != p[0] 时,此时 i应加一,即 应该 向右移动一个距离

                    显然 next[1] = 0;

                    然后从p[2]开始计算这时需要比较p[0]和p[1]

                                假设p[0]和p[1]相等,那么 next[2]就应该为1

                                 再去求next[3]时, 由于p[1] == p[0],这时只用继续比较p[2]和p[1]是否相等了

                    由上面的简但陈述可以看出,这其实就是模式串和自身的匹配过程;

                    只需要建立两个计数器i,j,i从1开始,j从0开始

                                                           如果 p[i] == p[j]  则 next[i+1] = j+1,i++,j++;

                                                           否则 j = next[j]

                                                           一直到遍历到串尾

这是代码:

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define MAX 2100700



//截取原串的所有长度等于模式串p的所有子串; 
int cmp0(string s,string p){
    //以i开头,长度为p.length() 
	for(int i=0;i<s.length();++i)
		if(s.substr(i,p.length()) == p)return i;
	return -1;
}

int cmp0(char *s,char *p){
	int slen=strlen(s),plen=strlen(p); 
	for(int i=0;i<slen;++i){
	    int j=i,k=0;
		while(j<slen && k<plen && s[j] == p[k])j++,k++;
		if(k == plen)return i;	
	}
	return -1;
} 

//使用string类进行查找 
void Next(string p,int *next){
	next[0]=-1,next[1]=0;
	for(int i=1,j=0;i<p.length();){
		if(j == -1 ||p[i] == p[j]){
			++i,++j;
			next[i]=j;
		}else j=next[j];
	}
}

int kmp(string s,string p){
	int *next = new int[p.length()+1],cnt=0;
	Next(p,next);
	for(int i=0,j=0;i<s.length();){
		while(j == -1 || i<s.length() && j<p.length() && s[i] == p[j])++i,++j;
		if(j == p.length()){
		    cnt++;
		    j=next[j];
		}
		else j=next[j];
	}
	return cnt;
}


//使用字符数组查找 
void Next(char *p,int *next,int plen){
	next[0]=-1,next[1]=0;
	for(int i=1,j=0;i<plen;){
		if(j == -1 || p[i] == p[j]){
			++i,++j;
			next[i]=j;
		}else j=next[j]; 
	}
}
int kmp(char *s,char *p){
    int cnt=0,slen,plen,*next;
	slen=strlen(s),plen=strlen(p);	
	next = new int[plen+1];
	Next(p,next,plen);
    for(int i=0,j=0;i<slen;){
    	while(j == -1 || i<slen && j<plen && s[i] == p[j])++i,++j;
    	if(j == plen){
    	    cnt++;        
			j=next[j];    //这一步很关键,不然会超时 
    	}
    	else j=next[j];
    }
	return cnt;	
}

char s[MAX],p[MAX];
int main(){
	int n;  
    cin>>n;
    while(n--){	    
        cin>>p>>s;	
    	cout<<kmp(s,p)<<endl;
    }
	return 0;
}

    原文作者:KMP算法
    原文地址: https://blog.csdn.net/arz74/article/details/52471690
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞