我所理解的KMP

KMP算法是一種用於字符串匹配的算法,由D.E.Knuth與V.R.Pratt和J.H.Morris同時發現,所以叫KMP算法。

字符串匹配,就是有一個目標字符串S和模式字符串P,然後查找P在S中是否有出現,出現的話,位置是什麼地方。

最簡單粗暴的方法就是逐個字符比較,從S的第0個字符開始,和P的第0個字符比較,如果相等,再比較後面一個,如果在第n個出現不想等,那麼就把S置回第1個(上一次的後面一個),P置回第0個字符,然後再開始一輪比較,很明顯,這樣比較的效率非常低下,如果S串的長度爲m,P串的長度爲n,時間複雜度爲O(mn)。

KMP的思想是先對模式串P做一些預處理,然後在逐個比較的時候可以跳過一些明顯不等的比較(比如a=b,a<>c,那麼b必然是<>c的,不需要再比較)。

首先,對模式串P求出next函數,結果爲一個數組,next[j]的值表示P[0...j-1]中最長後綴的長度等於相同字符序列的前綴,求解next函數的規則:

next[j] = -1  j = 0

next[j] = max(k): 0<k<j   P[0...k-1]=P[j-k,j-1]

next[j] = 0  other

還是一樣的逐個字符比較,然後在發生不匹配的時候,並不是從頭再開始,對於next[j]>0的情況,可以直接跳轉到next[j]的位置再進行比較,跳過前面next[j]個字符的比較,因爲肯定是可以匹配的。

那麼問題來了:爲什麼直接跳轉到next[j]的位置再進行比較,而前面跳過的前面next[j]個字符肯定是匹配的呢?

原因就在next函數的定義,再來看一遍:next[j]的值表示P[0...j-1]中最長後綴的長度等於相同字符序列的前綴。根據這個定義,就是說如果next[j]=k>0,那麼P[0...k-1]=P[j-k,j-1],由於S[s...s+j-1]和P[0...j-1]是相等的,根據相等的傳遞性,S中目前匹配到的最後next[j]個字符肯定和P中的前next[j]個字符是匹配的。

看個直觀的例子:

S:x y x y u y x z

P:x y x y x

還是一個一個字符比較,前面第0,1,2,3個都是相等的,到第4個的時候,出現不想等,那麼,可以之後去比較P的next[4] = 2的位置,P[0]=S[2],P[1]=S[3],是不用比較的,因爲next[4] = 2,就表示P[0]=P[2],P[1]=P[3],而比較到第4個出現不等,證明前0,1,2,3個都是相等的,即P[2]=S[2],P[3]=S[3],這樣肯定可以推導出P[0]=S[2],P[1]=S[3]。

下面是KMP算法的一種C的代碼實現:

int KMPMAtch(char *s, chap *p) {
	int i, j, next[1000];
	i = j = 0;
	initNextValues(p, next);
	while(i<strlen(s)){
		if(j=-1 || s[i]==p[j]){
			i++;
			j++;
		} else {
			j=next[j];
		}
		if(j==<strlen(p)){
			return i-j;
		}
	}
}

void initNextValues(char *p, int *next) {
	int j,k;
    next[0]=-1;
    j=0;
    k=-1;
    while(j<strlen(p)-1)
    {
        if(k==-1||p[j]==p[k])
        {
            j++;
            k++;
            next[j]=k;
        }
        else
            k=next[k];
    }
}

注:本文參考了很多網上介紹KMP算法的資料和博客,實在是太多了,不一一寫明出處了。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章