天天看點

在一個字元串中查找是否出現子字元串

今天寫一篇文章,讨論如何判斷在一個字元數為N的字元串s1中是否存在一個字元數為k(k<N)的子字元串s2,并傳回第一次出現的位置,如果這篇文章對您有所幫助,不要吝啬對部落客點贊哦

1.首先最一般的算法就是将s1進行分割,将字串與s2進行比較,這種算法最壞情形下的時間複雜度為O(k*(N-k)),平均時間為k*(1+N-k)/2

2.對第一種算法稍微進行一些優化,我們首先在s1中查找s2的第一個字元,找到之後對後續字元進行比對,這樣使得平均情形有了較大改善

3.之後我應用了散列的想法,對這個問題重新進行考慮,假設s1字元串為A1A2……AN,s2字元串為P1P2……Pk,我們可以通過計算得到一個散列值Hp,并将該散列值與A1A2……Ak,A2A3……Ak+1,……進行比較,如果散列值比對,那我們再逐字元進行比較。

隻要hash函數設定恰當就可以實作如果AiAi+1……Ai+k-1已知那麼Ai+1Ai+2……Ai+k可以以常數的時間算出。這裡我寫的hash函數如下

unsigned int Hash(string s)
{
    unsigned int hashval ;
    int k = s.size();
    for(auto p : s)
    hashval = (hashval<<5)+p%32;
    return hashval%(int)pow(32,k-1);

}
    
           

通過移位可以在常數時間内計算出相鄰子串的hash值,這種算法的最壞情形運作時間為O(k+N)加上反駁假比對所需的時間

4.對上述算法加以改進,可以将hash的碰撞次數降為0,這樣運作時間就為O(k+N)

unsigned int Hash(string s)
{
    unsigned int hashval ;
    int k = s.size();
    for(auto p : s)
    hashval = (hashval<<5)+p%32;
    return hashval%(int)pow(32,k);

}
           

5.以上就是我利用已有知識所能想到的部分了,查閱相關論文後,了解到一個平均時間為O(N/k)的算法,下文将詳細介紹這種算法,文獻已标注在文章末尾。

為了友善表述,我們将s1稱為string,s2稱為pat

與第二種算法不同,這種算法通過pat中最右端的字元char來尋找子串,我們将與char對齊的string中字元的位置記為position,位置上的字元記為character

(1)如果character在pat中沒有出現,我們可以将position右移k個機關,而不是1個機關

(2)如果character在pat中出現但與position不比對,那我們從pat最右端尋找character第一次出現的位置,并通過移動使這兩個位置對齊

(3)如果char與position比對,那我們依次檢查char左邊緊挨的字元,重複前兩個步驟

但是,到這裡我們仍然存在改進的空間,在依次檢查字元時,遇到一對不比對的字元,這是我們将剛才已經檢查過的字元取出來作為子串,通過遞歸調用查詢出下一次該字元出現的位置,将這個偏移量與我們在(1)(2)中計算出的偏移量進行比較,取較大者作為position移動的值

以上叙述可能比較晦澀,下面以一個例子來進行說明(*為position)

pat: 		AT-THAT
string:	...	WHICH-FINALLY-HALTS.--AT-THAT-POINT...
				  *
           

‘F’未在pat中出現,是以右移7位

pat: 		       AT-THAT
string:	...	WHICH-FINALLY-HALTS.--AT-THAT-POINT...
                         *
           

‘-’在pat中出現,移動使其對齊

pat: 		           AT-THAT
string:	...	WHICH-FINALLY-HALTS.--AT-THAT-POINT...
                             *
           

‘T’比對,檢查下一位

pat: 		           AT-THAT
string:	...	WHICH-FINALLY-HALTS.--AT-THAT-POINT...
                            *
           

‘L’不存在右移七位

pat: 		                 AT-THAT
string:	...	WHICH-FINALLY-HALTS.--AT-THAT-POINT...
                                   *
           

繼續檢查

pat: 		                 AT-THAT
string:	...	WHICH-FINALLY-HALTS.--AT-THAT-POINT...
                                 *
           

在右側找到“AT”使之對齊

pat: 		                      AT-THAT
string:	...	WHICH-FINALLY-HALTS.--AT-THAT-POINT...
                                        *
           

全部比對,檢查完畢

下面附上僞代碼

stringlen <---- length of string
		i <---- patlen
 top:   if i > stringlen then return false
 		j<---- patlen
 loop:  if j=0 then return i+1
 		if string(i)=pat(j)
 		then 
 		j<----j-1
 		i<----i-1
 		goto loop
 		close
 		i<----i+max(delta1(string(i)),delta2(j))
 		goto top
           

最後附上我自己的實作,供大家學習參考

/*
The expected value of the number of inspected characters in string is c*(i+patlen),where c<1 and gets smaller as patlen increases
*/
#include <string>
#include <iostream>
#include <math.h>

using namespace std;


int issubstr(int start,string pat,string str)
{
    const int stringlen = str.size();
    const int patlen = pat.size();
    int i =start+patlen-1;
    int j;
    bool* inspect;
    inspect = new bool[128];
    for(auto k=0;k<i;k++)
    inspect[pat[k]%128]=true;


    int delta1,delta2;
    string substr;

    while(true){
        if(i >stringlen-1) return -1;
        j = patlen-1;
        while(true)
        {
        if(j < 0) return i+1;
        if(str[i]==pat[j])
        {
            j=j-1;
            i=i-1;
        }else break;

    }
    if(!inspect[str[i]%128])
        
    delta1 = patlen;
    else {
        for(auto k=j-1;k>=0;k--)
        if(pat[k]==str[i]){
        
        delta1 = j-k;
        break;

        }

        
    }
    if(j!=patlen-1)
    {
        substr = pat.substr(j+1,patlen-j-1);
        delta2= issubstr(i+2,substr,str)-i;

    }else delta2=0;
    

    if(delta2<0) return -1;
    else i = i + max(delta1,delta2);

    }

    

}

int main()
{
    string str1,str2;
    cout<<"Enter the str1: "<<endl;
    cin>>str1;
    
    cout<<"Enter the str2: "<<endl;
    cin>>str2;    
    int n=issubstr(0,str1,str2);
    if(n>0)
    cout<<n;
    else cout<<"Not Found!"<<endl; 


}
           
> R.S.Boyer and J.S.Moore,"A Fast String Searching Algorithm",Communications of the ACM,20(1977),762-772
           

繼續閱讀