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]不匹配 

                                       

           然後進行第二次匹配,從s[1] 開始,如圖,會在s[5]不匹配

                                 

             在這兩次中,第二次的匹配是從 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;
}



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章