細聊模式匹配演算法

2021-09-02 09:58:00 字數 4484 閱讀 1011

在經典模式匹配問題中,我們經常給出了長度為n的文字字串t和長度為m的模式字串p,並希望明確是否p是t的乙個子串。如果是,則希望找到p在t中開始位置的最低索引 j,比如 t[ j: j+m] 和p匹配,或者從t中找到所有p的開始位置索引。

模式匹配問題在python的str類中有許多內在的行為,例如 p in t,t.find(p),t.index(p),以及t.count(p),這些行為是更複雜的行為中的子任務,例如 t.partition(p)、t.split(p) 和t.replace(p,q)。

接下來討論常提到的三種模式匹配演算法,難度逐漸遞減。

kmp演算法的主要思想是預先計算模式部分之間的自重疊,從而當不匹配發生在乙個位置時,我們繼續搜尋之前就能立刻知道移動模式的最大數目,從而避免浪費的匹配。它能達到的執行時間為o(m+n), 這也是漸近最優執行時間。即在最壞情況下,任何模式匹配演算法將會對文字所有字元和模式的所有字元檢查至少一次。

失敗函式

為了實現kmp演算法,我們要先計算乙個失敗函式f,該函式用於表示匹配失敗時p對應的位移。具體地,失敗函式 f(k) 定義為p的最長字首長度,它是p[1: k+1] 的字尾。直觀講,如果在字元p[ k+1]中找到不匹配,函式 f(k) 會告訴我們多少緊接的字元可以用來重啟模式。接下來實現一下失敗函式 kmp_fail :

def kmp_fail(p):

"""模式字串p

"""m = len(p)

fail = [0] * m # 初始化長度為m的陣列

j = 1 # 自比較,j從1開始,k從0開始

k = 0

while j < m:

if p[j] == p[k]:

fail[j] = k+1 # 值相同,陣列對應位置設為 k+1,可以理解為此處值與自身第 k+1個數相同

j += 1

k += 1

elif k > 0:

k = fail[k-1] # 值不同,則讓 k從前一位開始繼續匹配

else:

j+=1 # 如果從開始就沒有匹配到,則讓j繼續移動,k不變

return fail

一定要明白失敗函式是自身與自身的比較,所以 j 索引從1 開始,從0開始就沒有意義了。

舉例 abacab 失敗函式 k

0123

45p[k]ab

acab

f(k)00

1012

kmp模式實現

def find_kmp(t, p):

"""t 文字字串

p 模式字串

"""n, m = len(t), len(p)

if m == 0:

return 0

fail = kmp_fail(p) # 失敗函式返回乙個陣列

j = 0

k = 0

while j < n: # 對j=0開始遍歷整個t

if t[j] == p[k]:

if k == m-1:

return j - (m-1) # 對整個p匹配成功, 返回t中和p匹配的起始位置索引

j += 1

k += 1

elif k > 0:

k = fail[ k-1] # 中途匹配失敗,則從失敗陣列中取出下次要匹配的索引

else:

j += 1 # 從起始位置就沒有匹配, 讓j移動,k保持為0

return -1

當中 k = fail[k-1] ,為什麼傳k-1而不是k呢?這點比價難理解但是特別關鍵。p在k處匹配失敗,說明k-1處是匹配成功的,那麼從k-1處得到位移索引 k' 也是沒有問題的(也就是自身相同字串的末尾位數, 但是返回的是第幾位,而不是索引), 接下來也就是讓 t[j] 和 k' 處對應的值繼續比較也是沒問題的。

舉例:t : a  t  c  a  m  a  l  g  a  m  a  b  a  c  d  e  m  n  g  j  k  h  l  m  n  p  o  p  m  p  i  l  z  x  c  v  b  d   ...

p:             a  m  a  l  g  a  m  a t   i  o  n 

p:                                 a  m  a  l  g  a  m  a  t  i  o  n

amalgamation 失敗函式對應的位移 k

0123

4567

891011

p[k]am

alga

mati

onf(k)00

1001

2300

00當p中 't' 與t中 'b' 匹配失敗時,得到 't' 左邊 'a' 的移動值 3,此時t[11]和p[3]重新比較是很合理的。這就是amp的精華所在,也是提高效率的地方。如果此時t[11]和p[3]匹配失敗,那就是重新進入下一步 k = fail[k-1] 了。

效能分析

除去失敗函式的計算外,kmp演算法的執行時間明顯正比於while迴圈的次數。假設乙個長度為m的字串和自己進行比較,它的執行時間為o(m)。那麼長度為n的文字和長度為m的模式字串的匹配,執行時間是o(m+n)。之所以使用失敗函式,就是為了避免對模式中的字串和文字中的字串進行重複比較。

boyer-moore演算法的主要思想是通過增加兩個可能省時的啟發式演算法來提公升窮舉演算法的執行時間。大致如下:

看起來很頭暈,其實**裡體現很簡單。所以我喜歡敲**,不喜歡解釋。

def find_boyer_moore(t, p):

"""文字字串t

模式字串p

"""n, m = len(t), len(p)

if m == 0:

return 0

last = {} # 初始化乙個雜湊表,儲存p中元素最後出現時的索引

for k in range(m):

last[p[k]] = k

i = m - 1

k = m - 1

while i < n:

if t[i] == p[k]: # 從末尾到頭部一致匹配

if k == 0:

return i # m個字串正好匹配完全,返回索引i

else:

i -= 1

k -= 1

else:

j = last.get(t[i], -1) # 檢視last中是否存在匹配失敗的t[i],不存在返回-1

i += m - min(k, j+1) # 如果last中存在該值就要分析兩種情況了

k = m - 1

return -1

文字t:*    *    *    *    *    *    *    a    *    *    *    *    *    *    *

模式p:                   *    *   a    *    b    *    *    *                     第一種

模式p:                   *    *    *    *    b    a   *    *                     第二種

第一種,last 中的a在b的左側,也就是還未匹配的那部分,這時候要兩a相對應,i就要移動m-(j+1)的長度, k回到末尾重新開始。

第二種,last 中的a在b的右側,就是剛剛匹配過,i就要跳過剛匹配的部分,移動m-k的長度,k回到末尾重新開始迴圈。

如果t[i]在整個p中都不存在,也就是i 需要移動m個位置,直接跳過t[i],m回到p的末尾。

效能分析

如果使用傳統的查詢表,在最壞情況下執行時間是o(mm+ x),  最後乙個功能的實現需要o(m+x),但是在雜湊表的支援下,x的時間可以省掉, 但也達到o(mn)的效率,與窮舉演算法一樣。

要搜尋窮的功能,窮舉演算法設計模式是一種強大的技術。至於**就很easy了,其實剛工作時,不懂搜尋原理,寫了好多的窮舉**,想想也挺有意思,所以為什麼公司都想要招演算法功底強的程式設計師了,不熟悉演算法真是災難啊,哈哈...

def find_brute(t, p):

n, m = len(t), len(p)

for i in range(len(n-m+1)):

k = 0

while k < m and t[i+k] == p[k]:

k += 1

if k == m:

return i

return -1

效能分析

對窮舉演算法的分析就很簡單了,由兩個巢狀的迴圈組成,乙個基於文字字串,乙個基於模式字串,所以此演算法正確性就能得到保證了。最壞情況下執行時間是o(mn)。

模式匹配演算法

brute force演算法 kmp演算法 kmp演算法的改進 模式匹配 子串的定位操作被叫做串的模式匹配。串相等 串長度相等且各個對應位置的字元都相等。當兩個串不相等時,判斷兩個串大小的方法 給定兩個串 s1 a1a2a3a4 an 和s2 b1b2b3b4 bm 當滿足以下條件之一時,s1n存在...

演算法 模式匹配

你有兩個字串,即pattern和value。pattern字串由字母 a 和 b 組成,用於描述字串中的模式。例如,字串 catcatgocatgo 匹配模式 aabab 其中 cat 是 a go 是 b 該字串也匹配像 a ab 和 b 這樣的模式。但需注意 a 和 b 不能同時表示相同的字串。...

KMP模式匹配演算法以及普通模式匹配演算法

if return value 1 the indexsubstr is not exist else the indexsubstr is exist.int indexsubstr char substr,char str,int pos 0 printf lensubstr d n lensu...