模式匹配演算法KMP

2021-08-17 02:54:39 字數 4920 閱讀 5180

kmp演算法對bf演算法做了很大改進,是由克努特(knuth),莫里斯(morris),普拉特(pratt)同時發現。

簡單的模式匹配方法簡稱為bf演算法。

假設m,n是兩個串,m為主串,n為子串。在m中找到等於n的子串,則匹配成功,函式返回子串在主串首次出現的儲存位置,否則匹配失敗。(申明:本演算法中字串採用定長順序儲存,串的長度引數存放在0單元,串值從單元1開始儲存,字元序號與儲存位置一致)

簡單例項如下:

m:aacbcbaadbcbba

n:cbaa

用子串n的第乙個字元與主串m中的字元挨個對比,直到找到相同的字元,之後挨個比較是否相同,如果全都相同,那麼匹配成功,如果出現不相同的字元那麼再迴圈以上過程。

例項步驟如下:

第一趟:a

acbcbaadbcbba

(子串首字元在主串中挨個比較)

了l c

baa第二趟:a

acbcbaadbcbba

(上一趟比較不成功,則比較後乙個字元)

cbaa

第三趟:aa

cbcbaadbcbba

(比較成功,挨個比較後面的字元是否也相同)

cbaa

第四趟:aac

bcbaadbcbbacb

aa第五趟:aacb

cbaadbcbba

(後面的字元出現不行同的,則再次用子串首字元在主串中比較)cba

a第六趟:aacbc

baadbcbba

cbaa

第七趟:aacbcb

aadbcbba

cbaa

第八趟:aacbcba

adbcbbacb

aa第九趟:aacbcbaa

dbcbbacba

a第十趟:

aacbcbaa

dbcbba

(迴圈以上過程,最終找到匹配的子串)

cbaa

c語言實現如下:

int search_chuan(char m,char n)//模式匹配bf演算法,主串m,子串n

}if (j==n[0]+1)//兩種出迴圈的狀態,正常迴圈結束j指向子串末尾,非正常出迴圈j不會指向子串末尾}}

return 0;

}

bf演算法雖然簡單但是效率較低,造成bf演算法速度慢的原因主要在於,當子串後續匹配失敗時,將返回首字元匹配成功位置的下乙個位置,而這些回溯過程中有些是不必要的。

例如:在上面的例項中

第四趟:aac

bcbaadbcbbacb

aa第五趟:aacb

cbaadbcbbacba

a第六趟:aac

bcbaadbcbba

cbaa

第五趟匹配失敗後,回溯到首字元匹配成功位置的下一位置,第五趟表明子串前半部分cb是匹配的,所以回溯後首字元c必定再次與已知不匹配的b再比較一次,所以第六趟是不必要的,kmp演算法最大程度的避免了這樣的回溯,提高了執行效率。

綜上所述,在上述例項中我們希望在匹配失敗之後,不回溯,子串首字元比較的位置向後劃,跳過已知不會匹配成功的b字元。觀察上述例項可以發現,當子串中有相同字元段或相同字元時,若這一段字元已經匹配成功,那我們就知道主串上的相同位置的字元,我們希望利用這個已知資訊來避免回溯。

現在解決問題的關鍵在於比較的位置向後移動多少,在用例項進行推導時可以發現(這裡不再引入例項,有興趣可以自行研究),子串向後划動的長度與子串有關,這個演算法的關鍵就是確定上述關係,問題的數學模型建立如下。

設主串下標為i,子串下標為j,子串划動的最終位置為k,那麼k位置之前的字元段是相匹配的,所以有以下關係式成立

「n1,n2,n3,n4......nk-1」=「mi-k+1,mi-k+2,mi-k+3......mi-1」①

等號左邊是子串在主串中相匹配的字元段,等號右邊是匹配字元段在主串中的位置。

匹配失敗是在nj和mi處,所以得到的部分匹配結果為:

「n1,n2,n3,n4......nj-1」=「mi-j+1,mi-j+2,mi-j+3......mi-1」②

因為k「nj-k+1,nj-k+2,nj-k+3......nj-1」=「mi-k+1,mi-k+2,mi-k+3......mi-1」③

等號左邊為子串的前k-1項,等號右邊為主串的前k-1項

由①和③得:

「n1,n2,n3,n4......nk-1」=「nj-k+1,nj-k+2,nj-k+3......nj-1」④

結論:當在mi,nj時匹配失敗,如果子串滿足④式,那麼子串n可以向右划動至nk位置與主串對準,再繼續匹配。

上述過程實在太過於晦澀難懂了(我自己也是用例項推導了很多遍才勉強理解),並且很難理清(子串向後划動的長度與子串有關)這個關係,下面我引入另乙個研究過程和方法。

下面先解釋幾個詞彙:

字串字首:乙個字串除了最後乙個字元以外,其前面所有字元組成的字串的所有子字串的集合

字串:btboay

字串字首:「[b],[bt],[btb],[btbo],[btboa]」

字串字尾:乙個字串除了第乙個字元以外,其後面所有字元組成的字串的所有子字串的集合

字串:btboay

字串字尾:「[t],[tb],[tbo],[tboa],[tboay]」

部分匹配字元段:主串與子串相匹配的一段字元

主字串:btboay

子字串:boy

部分匹配字元段:bo        bt

boayboy

部分匹配值:字串的字首和字尾最長的共有元素長度

字串:btboay,其子字串的部分匹配值為

——「b」的字首和字尾都為空集,共有元素長度為0

——「bt」的字首為[b],字尾為[t],共有元素長度為0

——「btb」的字首為[b,bt],字尾為[tb,b],共有元素長度為1

——「btbo」的字首為[b,bt,btb],字尾為[tbo,bo,o],共有元素長度為0

——「btboa」的字首為[b,bt,btb,btbo],字尾為[tboa,boa,oa,a],共有元素長度為0

——「btboay」的字首為[b,bt,btb,btbo,btboa],字尾為[tboay,boay,oay,ay,y],共有元素長度為0

移動位數=已匹配字元數-對應的部分匹配值

了解以上的基本概念後,下面列舉幾個例項來解釋上面的計算式的由來:

例項一:

主串m:abcdeabcdf

子串n :abcdf

我們知道按照bf演算法的思想,在比較到f處中斷:

abcd

eabcdf

abcd

f子串後移一位:ab

cdeabcdf

abcdf

但實際上我們可以將子串直接移到這裡:

abcd

eabcdf

abcdf

為什麼我們敢確定前面的字元不會出現匹配的情況:在第一輪的匹配中前面的abcd字元是匹配的,很明顯這中間不可能有字元與首字元a相匹配,那是不是我們直接將子串移到下乙個出現首字元a的地方就可以呢?

請看下面的例子:

例項二:

主串m:abadeabadf

子串n :abadf

第一輪失敗:    

abad

eabadf

abadfab

adeabadf

abadf

這樣是可以的,簡化了bf演算法,但這樣是最佳嗎?在首輪匹配失敗後,已知匹配成功的字元為abad,在上述移動後,我們發現,其實我們已經知道第二個a後面是d,與b是不匹配的。

可以直接移動到這裡:

abad

eababf

ababf

看完這兩個例子,有人會想是不是直接移到匹配失敗的位置就好了?(呵呵,不要犯傻)

再看下面的例子:

例項三:

主串m:ababeababf

子串n: ababf

在字元f處匹配失敗後,子串可以直接移到這裡:

ababea

babf

abab

f主串m:abababf

子串n: ababf

在字元f處匹配失敗後,子串可以直接移到這裡:aba

babf

ababf

例項三的結果可以發現移動後有一些規律,可以用下圖來表示:

可以看到在已匹配的字元中a,b段是相同的,而這是已匹配字元段的字尾和字首的最長共有元素(也就是部分匹配段),而子字串移動位數k=已匹配字元長度-部分匹配值,這就是子字串移動位數計算式的由來。

如果我們提前將所有子字串的部分匹配值計算出來,就可以知道匹配失敗後的移動位數,所以在kmp演算法中我們建立next值表來儲存這些值(next值就是部分匹配值加1)。

下面我們利用例項詳細介紹求next值表的演算法:

子串(模式串):ababf

①:在字元a之前沒有字元,所以預設為0

②:在字元b之前只有字元a,無字首和字尾,所以next值為1

③:在字元a之前有字元ab,字首為a,字尾為b,無公共子串,所以next值為1

④:在字元b之前有字元aba,在步驟③中做了a和b的比較,所以不再需要比較,此時只需要比較新字元a和首字元a是否相同,在此時的例項中是相同的,所以next值為上乙個字元的next值加1,所以現在的next值為2

⑤:在字元f之前有字元abab,在步驟④已知有乙個相同的字元,此時移位與新字元b比較,發現依然相等,所以next值為3

依次類推

計算next值的函式c語言實現如下:

void next(char s,int next)

}if (j==n[0]+1)//兩種出迴圈的狀態,正常迴圈結束j指向子串末尾,非正常出迴圈j不會指向子串末尾

else}}

return 0;

}

模式匹配 KMP演算法

字串匹配演算法 include includeusing namespace std define ok 1 define error 0 define overflow 2 typedef int status define maxstrlen 255 使用者可在255以內定義最長串長 typed...

模式匹配KMP演算法

前些日子在為目前該學習什麼而苦惱,就問了一下已經從事多年軟體開發的表哥,他說乙個程式設計師要走的遠,就要學好資料結構和演算法,於是我就重新開始學習資料結構和演算法了 拿起以前上過的資料結構看,看到第四章串的模式匹配時,頗感興趣,就寫了一下程式,實踐了一下。感覺還蠻爽,於是就把以下幾個重要的函式放在此...

KMP模式匹配演算法

首先,這是由knuth morris和prattle三人設計的線性時間字串匹配演算法。這裡就不仔細的講解,網上有更好的講解,在這推薦幾個自己當時學習的kmp所看到的較好理解的 這裡附上自己所學的 includeusing namespace std s 是主串 p 是模式串 int next 100...