真正理解KMP演算法

2021-07-02 00:13:05 字數 3205 閱讀 9308

原帖:

所謂kmp演算法,就是判斷乙個模式串是否是乙個字串的子串,通常的演算法當模式串失配後需要回溯原串和模式串,原串從上次開始匹配的下乙個字母開始來匹配模式串的第乙個字母。舉乙個例子,原串為abababcd,模式串為ababcd,如圖1一直從頭開始匹配,當匹配到第5個紅色字母時,發現a和c失配,通常的演算法需要回溯原串從第二個字元開始,模式串從第乙個字元開始重新匹配,如圖2所示:

圖1 字串失配

圖2 傳統方法回溯

我們發現,回溯原串是不必要的,因為我們可以發現,回溯到圖二的情況時一定會失配,原因是在圖1中模式串的前兩個字母已經和原串的前兩個字母匹配,並且模式串的前兩個字母不同,所以當模式串第乙個字母和原串的字母一定失配,那麼我們就沒有必要去回溯原串,只需把模式串向右移動就行了。那麼向右移動到什麼位置呢?如圖3所示,我們發現模式串的字首深紅色的ab與模式串失配的第5個字元c之前的綠色字尾ab完全相同,有因為綠色的模式串字尾ab與原串匹配,因此我們可以斷定模式串的字首ab一定可以和原串綠色的ab相匹配,從而直接可以把模式串向後移動兩位,得到圖4的樣子,繼續進行匹配。

圖3 模式串的前字尾相同

圖4 kmp演算法失配後向右移動兩位

那麼我們如何計算模式串每次移動的位置呢,對於模式串的每一位我們都要預處理出乙個其失配後需要移動到的位置,即next陣列,其中next陣列第i位的含義為:模式串開始到第i位之前的字串的字首字串與字尾字串相同的最大長度,並另next[0]=-1。

從而我們可以口算出ababcd的next陣列:

表1 口算next陣列

模式串ab

abcd

字首字尾匹配無無

無字首a=字尾a

字首ab=字尾ab

無next-10

0120

多算幾個模式串的next陣列我們就會發現,我們可以利用前面的字母的next陣列的值來計算當前字母的next陣列,例如對與ababcd中的第5個字母c,因為他的前乙個字母b的next陣列的值為1也就是說第1個字首字母a和b的字尾字母a相同,我們並不需要再次進行比較,而是直接比較p[4]與p[next[4]]是否相同我們發現都是b,從而第5個字元c的next陣列的值就等於其前乙個字母b的next陣列值+1。如果不匹配怎麼辦?我們只需再次向前比較p[4]與p[next[next[4]]即可,一直到相等或者next陣列的值為-1。這樣我們就可以很輕鬆的計算next陣列了。

其計算next陣列和kmp匹配的**如下,其中kmp函式返回模式串與原串匹配的次數:

1 #include 2 #include 3 #include 

4 #include 5 #include 6 #include 7

8using

namespace

std;

9 vector getnext(string

p)10

23else

2427}28

return

next;29}

3031

int kmp(string p, string s, vectornext)

3243

else

if( j == -1 || s[i] ==p[j] )

4448

else

4952}53

if( s[i] ==p[j] )

5457

return

res;58}

5960

int main(int argc, char *argv)

6172 }

next陣列的優化:使用這種方法計算next陣列時我們會發現乙個問題,例如對於模式串為abab,原串為abacabab的字串。我們可以得到模式串的next陣列為-1,0,0,1。從而當其進行匹配第一次失配時如圖5所示,根據失配的第4個b的next值為1,從而模式串下標為1的字母b與原串再次匹配,如圖6所示:

圖5 失配

圖6 模式串下標為1的字母b與其進行匹配

我們可以發現,這次的匹配一定使失敗的,因為在我們的模式串的下標為3的字母b,其p[3]=p[next[3]]='b',有因為p[3]與原串失配,所以p[next[3]]也一定與原串失配,因此我們在構造模式串時,需要判斷p[i]是否與p[next[i]]相等,如果不想等,賦值next即可,如果相等,則需要把對next[next[i]的值賦給next[i],對於這個例子,因為p[3]=p[next[3]]='b',所以另next[3]=next[next[3]] = -1。即可得到優化後的next陣列。為-1,0,0,-1。

**如下:

1 #include 2 #include 3 #include 

4 #include 5 #include 6 #include 7

8using

namespace

std;

9 vector getnext(string

p)10

25else

2629}30

else

3134}35

return

next;36}

3738

int kmp(string p, string s, vectornext)

3950

else

if( j == -1 || s[i] ==p[j] )

5155

else

5659}60

if( s[i] ==p[j] )

6164

return

res;65}

6667

int main(int argc, char *argv)

6879 }

字元匹配 真正理解KMP演算法的力量 修正

分析 這道題很自然的想法就是 查詢 回溯,見 find 1 函式。此演算法最易想到,可是時間複雜度為 o m n 其中m,n 分別為s,t的大小。此類字元匹配有乙個經典演算法,kmp演算法 由knuth morris pratt共同提出 這個演算法非常好的 預處理 了要用於查詢的t.即發現t串的特點...

真正理解java wait notify

從字面上理解,notify 方法能夠喚醒乙個正在等待該物件的monitor的執行緒,當有多個執行緒都在等待該物件的monitor的話,則只能喚醒其中乙個執行緒,具體喚醒哪個執行緒則不得而知。nofityall 方法能夠喚醒所有正在等待該物件的monitor的執行緒,這一點與notify 方法是不同的...

真正理解矩陣

孟巖,從很獨特的角度理解矩陣。理解矩陣 一 理解矩陣 二 理解矩陣 三 這裡的運動不同於物理中連續的運動,而是瞬間的從一點到另一點的運動 即躍遷 術語為 變換 因此,矩陣是對線性空間裡變換 即線性變換 的描述。選的基 座標系 不同,同乙個變換就有不同的描述,即有不同的矩陣,這些矩陣是相似的,矩陣a,...