1. 程式人生 > >串-模式匹配-KMP

串-模式匹配-KMP

子串的定位操作通常稱作串的模式匹配,是各種串處理系統中最重要的操作之一。

演算法有BF蠻力演算法和KMP演算法,KMP演算法的特點是速度快。可以在O(n+m)的時間數量級上完成串的模式匹配操作。

演算法思想:

與蠻力演算法相比,其改進在於,每當一趟匹配過程中出現字元比較不等時,不需回溯i指標,而是利用已經得到的部分匹配的結果將模式向右滑動儘可能遠的一段距離後,繼續進行比較。

S.ch[i]與T.ch[j]失配時,只需找到S中截止到S[i-1]的真字尾,它與T中T.ch[i]開始的字首相等且該子串儘量長,S.ch[i]接下來應該比較的字元記為T[k]。如何計算k呢?

S[i]與T[1]不配,則S[i]不應與T中元素比較,next[1]=0,i後移

若S[i]與 T[2]不配,只能再拿S[i]與T[1]比,故next[2]=1

假設j>=3 則找S中截止到S[i-1]的真字尾,它與T中T[1]開始的字首相等並且子串儘量長,next函式值為子串長度+1

演算法實現:

int Index_KMP(HString S,HString T,int pos){
	//利用模式串T中額next函式求T在主串S中第pos個字元之後位置的
	//KMP演算法 。T非空 pos>=1&&pos<=S.length
    if(pos<1||pos>S.length||!T.length) //pos值非法
         return ERROR;
    int i=pos,j=1,nextval[100];
    Get_Nextval(T,nextval); //獲得T中next值
    while(i<=S.length&&j<=T.length){
        if(j==0||S.ch[i-1]==T.ch[j-1]){ //j為0代表next[j]返回值0
		 //繼續比較後續字元
            i++;
            j++;
		}
        else
           j=nextval[j]; //模式串後移 i不變j後退
	}
    if(j>T.length)//匹配成功
        return i-T.length;
    return 0;
}

時間複雜度:

設主串的長度為n,模式串長度為m,在KMP演算法中求next陣列的時間複雜度為O(m),在後面的匹配中因主串S的下標不回溯,而且子串指標每後移一次則主串指標也後移,此時導致的移動總次數不會超過n+m,故複雜度O(n+m)。

遞推法求next函式值:

初值next[1]=0;

假設next[i-1]的值為k,此時T[1..k-1]=T[i-k...i-2]。

若T[k]=T[i-1], next[i]=k+1。

若T[k]!=T[i-1],說明T[1..k]與T[i-k...i-1]只最後一個字元不匹配。如此應讓T[i-1]與T[next[k]]比較,或說重置上次匹配時的next值k為next[k],即令k=next[k];

重複上述步驟,直到T[i-1]=T[k]或k=0,前者next[i]=k+1 後者next[i]=1.

void Get_Next(HString T, int next[]){
	//求模式串T中的next值並存入陣列next。
    int i=1,j=0;
    next[1]=0;
    while(i<T.length){
        if(j==0||T.ch[i-1]==T.ch[j-1]){ //如果T.ch[i-1]==T.ch[j-1] 表明真右字串與前面左字串相等
		//獲得next值 並比較後續字元
           i++;
           j++;
           next[i]=j;
		}
        else //說明只最後字元不匹配 這時應該T.ch[i-1]==T.ch[next[j]-1] 比較 即j=next[j];
            j=next[j];
	}
}

改進的nextval函式:

void Get_Nextval(HString T, int nextval[]) {
	//改進的求模式串T中的nextval值並存入陣列nextval。
     int i=1,j=0;
     nextval[1]=0;
     while(i<T.length){
        if(j==0||T.ch[i-1]==T.ch[j-1]){
           i++;
           j++;
           if(T.ch[i-1]!=T.ch[j-1])
               nextval[i]=j;
           else//如果字元與前一個相等 不需要比較 直接使比較T.ch[next[j]-1] 即 nextval[i]=nextval[j];
               nextval[i]=nextval[j];
		}
        else
            j=nextval[j];
	 }
}

下面來看一下用這個演算法實際解決的一個問題吧。

用V替換S中出現的所有與T相等的不重疊的字串。

Status Replace(HString &S,HString T,HString V){
	//用V替換S中出現的所有與T相等的不重疊的字串
    int pos=1;
    while(pos){//只要還能找到字串
       pos=Index_KMP(S,T,pos);//從pos位置開始找 返回找到的位置
       StrDelete(S,pos,T.length); //pos位置刪除這個串T
       StrInsert(S,pos,V); //pos位置插入串V
	}
    return OK;
}