淺談Python描述資料結構之KMP篇
前言
本篇章主要介紹串的KMP模式匹配演算法及其改進,並用Python實現KMP演算法。
1. BF演算法
BF演算法,即
假設主串
def BF(substrS,substrT): if len(substrT) > len(substrS): return -1 j = 0 t = 0 while j < len(substrS) and t < len(substrT): if substrT[t] == substrS[j]: j += 1 t += 1 else: j = j - t + 1 t = 0 if t == len(substrT): return j - t else: return -1
2. KMP演算法
KMP演算法,是由
就是這次匹配失敗時,下次匹配時模式串應該從哪一位開始比較。
BF演算法思路簡單,便於理解,但是在執行時效率太低。在上述的匹配過程中,第一次匹配時已經匹配的
字首:是指除最後一個字元外,字串的所有頭部子串。
字尾:是指除第一個字元外,字串的所有尾部子串。
部分匹配值
例如,
字首一定包含第一個字元,字尾一定包含最後一個字元。
如果模式串1號位與主串當前位(箭頭所指的位置)不匹配,將模式串1號位與主串的下一位進行比較。next[0]=-1,這邊就是一個特殊位置了,即如果主串與模式串的第1位不相同,那麼下次就直接比較各第2位的字元。
如果模式串2號位與主串當前位不匹配,找最長公共前後綴,指標前面的子串為
如果模式串3號位與主串當前位不匹配,找最長公共前後綴,指標前面的子串為
如果模式串4號位與主串當前位不匹配,找最長公共前後綴,指標前面的子串為
如果模式串5號位與主串當前位不匹配,找最長公共前後綴,指標前面的子串為
如果模式串6號位與主串當前位不匹配,找最長公共前後綴,指標前面的子串為
如果模式串7號位與主串當前位不匹配,找最長公共前後綴,指標前面的子串為
如果模式串8號位與主串當前位不匹配,找最長公共前後綴,指標前面的子串為
綜上,可以得到模式串的next陣列,發現沒有,把主串去掉也可以得到這個陣列,即下次匹配時模式串向後移動的位數與主串無關,僅與模式串本身有關。
位編號 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
索引 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
模式串 | A | B | A | A | B | C | A | C |
next | -1 | 0 | 0 | 1 | 1 | 2 | 0 | 1 |
next陣列,即存放的是每個字元匹配失敗時,對應的下一次匹配時模式串開始匹配的位置。
如何在程式碼裡實現上述流程呢?舉個栗子,藍色方框圈出的就是公共前後綴,假設next[j]=t:
當
當
程式碼如下:
def getNext(substrT): next_list = [-1 for i in range(len(substrT))] j = 0 t = -1 while j < len(substrT) - 1: if t == -1 or substrT[j] == substrT[t]: j += 1 t += 1 # Tj=Tt,則可以到的next[j+1]=t+1 next_list[j] = t else: # Tj!=Tt,模式串T索引為t的字元與當前位進行匹配 t = next_list[t] return next_list def KMP(substrS,substrT,next_list): count = 0 j = 0 t = 0 while j < len(substrS) and t < len(substrT): if substrS[j] == substrT[t] or t == -1: # t == -1目的就是第一位匹配失敗時 # 主串位置加1,匹配串回到第一個位置(索引為0) # 匹配成功,主串和模式串指標都後移一位 j += 1 t += 1 else: # 匹配失敗,模式串索引為t的字元與當前位進行比較 count += 1 t = next_list[t] if t == len(substrT): # 這裡返回的是索引 return j - t,count+1 else: return -1,count+1
3. KMP演算法優化版
上面定義的next陣列在某些情況下還有些缺陷,發現沒有,在第一個圖中,我們還可以跳過第3次匹配,直接進行第4次匹配。為了更好地說明問題,我們以下面這種情況為例,來優化一下KMP演算法。假設主串
可以看到第2、3、4次的匹配是多餘的,因為我們在第一次匹配時,主串
那麼,問題出在哪裡???我們結合著next陣列看一下:
位編號 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
索引 | 0 | 1 | 2 | 3 | 4 |
模式串 | A | A | A | A | B |
next | -1 | 0 | 1 | 2 | 3 |
問題在於,當
所以,我們要修正一下next陣列。
大致流程和上面求解next陣列時一樣,這裡就是多了一個判別條件,如果在匹配時出現了
程式碼如下:
def getNextval(substrT): nextval_list = [-1 for i in range(len(substrT))] j = 0 t = -1 while j < len(substrT) - 1: if t == -1 or substrT[j] == substrT[t]: j += 1 t += 1 if substrT[j] != substrT[t]: # Tj=Tt,但T(j+1)!=T(t+1),這個就和next陣列計算時是一樣的 # 可以得到nextval[j+1]=t+1 nextval_list[j] = t else: # Tj=Tt,且T(j+1)==T(t+1),這個就是next陣列需要更新的 # nextval[j+1]=上一次的nextval_list[t] nextval_list[j] = nextval_list[t] else: # 匹配失敗,模式串索引為t的字元與當前位進行比較 t = nextval_list[t] return nextval_list
對KMP的優化其實就是對next陣列的優化,修正後的next陣列,即nextval陣列如下:
位編號 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
索引 | 0 | 1 | 2 | 3 | 4 |
模式串 | A | A | A | A | B |
nextval | -1 | -1 | -1 | -1 | 3 |
下面就測試一下:
if __name__ == '__main__': S1 = 'ABACABAB' T1 = 'ABAB' S2 = 'AAABAAAAB' T2 = 'AAAAB' print('*' * 50) print('主串S={0}與模式串T={1}進行匹配'.format(S1,T1)) print('{:*^25}'.format('KMP')) next_list1 = getNext(T1) print('next陣列為: {}'.format(next_list1)) index1_1,count1_1 = KMP(S1,T1,next_list1) print('匹配到的位置(索引): {},匹配次數: {}'.format(index1_1,count1_1)) print('{:*^25}'.format('KMP優化版')) nextval_list1 = getNextval(T1) print('nextval陣列為: {}'.format(nextval_list1)) index1_2,count1_2 = KMP(S1,nextval_list1) print('匹配到的位置(索引): {},匹配次數: {}'.format(index1_2,count1_2)) print('') print('*' * 50) print('主串S={0}與模式串T={1}進行匹配'.format(S2,T2)) print('{:*^25}'.format('KMP')) next_list2 = getNext(T2) print('next陣列為: {}'.format(next_list2)) index2_1,count2_1 = KMP(S2,T2,next_list2) print('匹配到的位置(索引): {},匹配次數: {}'.format(index2_1,count2_1)) print('{:*^25}'.format('KMP優化版')) nextval_list2 = getNextval(T2) print('nextval陣列為: {}'.format(nextval_list2)) index2_2,count2_2 = KMP(S2,nextval_list2) print('匹配到的位置(索引): {},匹配次數: {}'.format(index2_2,count2_2))
執行結果如下:
執行的結果和我們分析的是一樣的,不修正next陣列時,主串
結束語
在寫本篇部落格之前也是反覆看參考書、視訊,邊畫圖邊去理解它,這篇部落格也是反覆修改了好幾次,最終算是把KMP解決掉了,有關字串知識的複習也算是基本結束,下面就是刷題了(雖然在LeetCode做過了幾道題)。
到此這篇關於Python描述資料結構之KMP篇的文章就介紹到這了,更多相關Python KMP內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!