四大字串匹配演算法總結

2021-10-11 04:01:00 字數 3915 閱讀 6053

首先簡單介紹一下字串匹配問題,字串匹配問題裡面包含乙個文字串和乙個模式串。我們的目標是找到文字串中與模式串相同的子字串,該問題就稱之為字串匹配問題。

樸素字串匹配演算法其實就是暴力對比的原理,因為模式字串所有可能的開頭只有文字串中每乙個字元的位置,所以我們只需要判斷以文字串中每乙個字元打頭時,模式串是否可以匹配文字串。

//樸素演算法

bool

ncmp

(string str,string p)}if

(tag)

return

true;}

return

false

;}

樸素演算法的時間複雜度是(n-m-1)m的,其中n是文字串長度,m是模式串長度,當m長度接近n時,演算法的時間複雜度就是o(n^2)的,所以針對大規模的字串來說,該演算法的效能並不高,之所以造成這種情況,是因為他沒有有效的利用文字串每一位與模式串比較時的結果,也即假如每一位從開頭就比較失敗,那麼演算法的時間複雜度是o(n)的,此時我們可以認為是利用了每一步文字串s與模式串p比較時的資訊,而當p與s比較成功時,此時已經比較過的匹配成功字元的資訊我們並沒有在後續比較過程中加以利用,例如在s=abcabcd和p=abcd這種情況下,p首先從s的第0位嘗試比較,比較失敗;接下來樸素演算法會選擇s的第二位重複操作,而沒有利用第一步時p[1]=s[1],p[2]=s[2]的資訊,如果我們有效利用該資訊,並知曉p[0]!=p[1] ,p[0]!=p[2],那麼下一步時我們可以直接從s[3]開始匹配,而跳過s[1],與s[2]的匹配過程,這顯然會很大程度地降低時間複雜度。

考慮到樸素演算法的缺陷,後面三種演算法都是對模式串進行預處理,來儲存之前比較時可利用的資訊。

rk演算法的預處理時間複雜度是o(n),而最壞的情況下,時間複雜度是o((n-m+1)*m)的,但是基於一些假設,該演算法的平均時間複雜度是較好的。

該演算法運用了初等數論的概念,假設字符集只包含,那麼字串"12345",對應數字就是12345,對於乙個字符集只包含0~9的文字串而言,對於模式串p來說,我們可以計算出p對應的值,然後我們用這個唯一的數字代替該字串,現在我們的比較過程就是用p對應的數,和文字串s中每乙個p將要匹配的子串所對應的整數進行比較,如果兩個相等就證明原始字串相等。

可以看出該方法實際上就是將字串對映到了乙個數,而將字串比較與數掛鉤,實際上的思想與雜湊函式的思想是類似的。

現在的問題是如何快速計算出模式串對應的數,以及文字串s中每乙個與p長度相同的子串對應的值,我們以s=「1231321」,p="313"為例,我們可以在o(m)的時間複雜度內求得p對應的值,而如何在o(n)的時間複雜度內求出s所有長度與p相等的子串對應的值呢?首先我們可以求得s[0]打頭的字串對應的數num[0]是123,我們如何由num[0]求解出num[1]呢?這實際上就是乙個簡單的差分陣列的問題,我們可以由num[1]=(num[0]-s[0] * 10^m)+s[1+p];快速求出s中每一位打頭的長度等於p的子串對應的nums[i],此時我們只需要比較對應的p的值和nums[i]的值是否相等即可;

另乙個問題是當m較大時,由於數也會變得很大,這樣數的比較就不是o(1)時間複雜度了,所以這裡我們利用模相等的思想來判斷是否匹配,具體來說就是(p=nums[i])mod q,時p可能與s中i打頭的子串匹配,之所以是可能只因為,對於大數來說模相等,不意味原值相等,但是模不相等,原值一定不相等。

這樣我們就可以利用模等式來排除不可能相等的,而只比較模相等的情況,這也是我們為什麼說該演算法最壞條件下的時間複雜度是o((n-m+1)*m)的原因。

#include

using

namespace std;

bool

check

(int k,string &str,string &s)

return

true;}

}return

false;}

intmain()

cout

long

long num[n]

; num[0]

=0;for

(int i=

0;i(num[0]

==nump)

long

long e=1;

for(

int i=

0;i1;i++

) cout

int i=

1;i1;i++)}

cout<<

"false"

}

上述**可以處理字符集為數字的所有字串,而對於不同的字符集,可以設定不同的基數。

這種方法其實是編譯原理裡面的重點內容,每乙個有限自動機都對應乙個正規表示式,每個有限自動機代表的是復合正規表示式的一類字串,他可以匹配的範圍更大,當然也可以匹配單個字串。

缺點是這種方式下的有限自動機可能會很大,這導致預處理時間會比較複雜。

有限狀態自動機是乙個五元組(q,q0,a,∑,σ),其中

q是狀態的所有集合。

q0∈q是初始狀態。

a⊆q是乙個特殊的接受狀態集合。

∑是乙個有限輸入字母表

σ是乙個q×∑到q的函式,稱之為轉移函式。

有限自動機從q0開始,每次讀入輸入字串的乙個字元,如果有限自動機在狀態q時讀入了字元a,則它從狀態q變成了σ(q,a)(進行了一次轉移)每當其當前狀態q屬於a時,就說自動機接受了字串a。

具體如何構造自動機,其實本質上的思想與kmp的思想是一致的,它要最大程度地利用已經比較過的字串的狀況來確定當某個字串匹配不成功時需要轉移到的狀態,區別是dfa採用的是乙個乙個匹配的狀態來表示這個中間過程,而kmp演算法則是用一維將狀態轉移轉化成成了模式串位置的轉移,這裡以後有機會在詳細介紹。

與前面dfa的思想一致,但是kmp演算法不顯式計算狀態轉移函式,而是使用乙個輔助函式π,它在o(m)的時間複雜度內計算出來,並儲存到π[1~m]陣列中,陣列π可以按照需要及時有效的計算,在攤還的意義上與dfa中的狀態轉移函式一致。

粗略的說dfa方法包含了很多無用的,但是構建dfa需要的狀態轉換函式,所以預處理時,需要乙個字符集的係數。

現在詳細介紹kmp演算法的原理,kmp演算法中的陣列π儲存的是模式與其自身偏移量的匹配資訊,換句話說對於乙個模式串ababaca來說,當模式串與文字串s在模式串第5個字元匹配失敗(字元下標從0開始),也即此時p的字首ababa與s匹配,而c與字串s對應位置的字元不匹配,那麼此時我們想選擇ababa的可以與其真字尾匹配的最大字首作為已經與s匹配成功的字首,也即我們應該選擇aba作為ababaca在字元c匹配不成功時選擇的新的與s匹配的字首,來繼續之後的匹配過程。

換句話說已經匹配成功的字串我們可以任務是s中的,那麼我們在某一位匹配失敗時,肯定要選擇p的某個字首,使得該字首與已經匹配成功的字串的字尾匹配,我們可以不斷迭代這個過程,知道s未與p匹配成功的字串與p中的某個字元匹配,或者p以該字元的下乙個位置為開頭進行匹配。

現在的問題是我們怎麼構造π陣列,由於π陣列中應該儲存的是最大真字首的長度,所以我們可以儲存p的頭部字元對應的文字串s中的下標所對應的偏移量,也即對於模式串第i位匹配失敗時,π應當滿足π[i]+真字首的長度=i;

具體的計算π陣列的方式如下:

#include

using

namespace std;

intmain()

for(

int i=

0;i)cout<<<

" ";cout

;//模式串當前下標

//匹配過程與預處理過程其實是一致的。

for(

int i=

0;i(s[q+1]

==str[i]

)q++

;//能匹配成功時模式串匹配長度加1

if(q==m-1)

//匹配到了m-1位時輸出true

} cout<<

"false"

}

字串匹配演算法 字串匹配演算法總覽

字串匹配在文字處理裡非常重要,我們採用簡潔的python 把以下演算法一一實現並講解。樸素演算法 algorithm rabin karp 演算法 有限自動機演算法 finite automation knuth morris pratt 演算法 kmp algorithm boyer moore ...

字串匹配演算法

首先引用一下另一篇文章中對字串匹配的介紹 字串匹配指的是從文字中找出給定字串 稱為模式 的乙個或所有出現的位置。本文的演算法一律輸出全部的匹配位 置。模式串在 中用x m 來表示,文字用y n 來,而所有字串都構造自乙個有限集的字母表 其大小為 根 據先給出模式還是先給出文字,字串匹配分為兩類方法 ...

字串匹配演算法

平常操作文字的時候,經常需要操作對字串進行操作。而字串中最重要的一種操作就叫匹配,字串的匹配演算法很多,人們最熟悉的莫過於kmp演算法了。今天就來談一談一些字串匹配演算法。先來說說大名鼎鼎的kmp演算法,這個演算法出現在無數的資料結構與演算法書上面。它的策略很簡單 當模式串第k個字元不匹配主串中第s...