淺談Python描述數(shù)據(jù)結(jié)構(gòu)之KMP篇
前言
本篇章主要介紹串的KMP模式匹配算法及其改進(jìn),并用Python實現(xiàn)KMP算法。
1. BF算法
BF算法,即
假設(shè)主串
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算法,是由
就是這次匹配失敗時,下次匹配時模式串應(yīng)該從哪一位開始比較。
BF算法思路簡單,便于理解,但是在執(zhí)行時效率太低。在上述的匹配過程中,第一次匹配時已經(jīng)匹配的
前綴:是指除最后一個字符外,字符串的所有頭部子串。
后綴:是指除第一個字符外,字符串的所有尾部子串。
部分匹配值
例如,
前綴一定包含第一個字符,后綴一定包含最后一個字符。
如果模式串1號位與主串當(dāng)前位(箭頭所指的位置)不匹配,將模式串1號位與主串的下一位進(jìn)行比較。next[0]=-1,這邊就是一個特殊位置了,即如果主串與模式串的第1位不相同,那么下次就直接比較各第2位的字符。
如果模式串2號位與主串當(dāng)前位不匹配,找最長公共前后綴,指針前面的子串為
如果模式串3號位與主串當(dāng)前位不匹配,找最長公共前后綴,指針前面的子串為
如果模式串4號位與主串當(dāng)前位不匹配,找最長公共前后綴,指針前面的子串為
如果模式串5號位與主串當(dāng)前位不匹配,找最長公共前后綴,指針前面的子串為
如果模式串6號位與主串當(dāng)前位不匹配,找最長公共前后綴,指針前面的子串為
如果模式串7號位與主串當(dāng)前位不匹配,找最長公共前后綴,指針前面的子串為
如果模式串8號位與主串當(dāng)前位不匹配,找最長公共前后綴,指針前面的子串為
綜上,可以得到模式串的next數(shù)組,發(fā)現(xiàn)沒有,把主串去掉也可以得到這個數(shù)組,即下次匹配時模式串向后移動的位數(shù)與主串無關(guān),僅與模式串本身有關(guān)。
位編號 | 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數(shù)組,即存放的是每個字符匹配失敗時,對應(yīng)的下一次匹配時模式串開始匹配的位置。
如何在代碼里實現(xiàn)上述流程呢?舉個栗子,藍(lán)色方框圈出的就是公共前后綴,假設(shè)next[j]=t:
當(dāng)
當(dāng)
代碼如下:
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的字符與當(dāng)前位進(jìn)行匹配 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的字符與當(dāng)前位進(jìn)行比較 count += 1 t = next_list[t] if t == len(substrT): # 這里返回的是索引 return j - t, count+1 else: return -1, count+1
3. KMP算法優(yōu)化版
上面定義的next數(shù)組在某些情況下還有些缺陷,發(fā)現(xiàn)沒有,在第一個圖中,我們還可以跳過第3次匹配,直接進(jìn)行第4次匹配。為了更好地說明問題,我們以下面這種情況為例,來優(yōu)化一下KMP算法。假設(shè)主串
可以看到第2、3、4次的匹配是多余的,因為我們在第一次匹配時,主串
那么,問題出在哪里???我們結(jié)合著next數(shù)組看一下:
位編號 | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
索引 | 0 | 1 | 2 | 3 | 4 |
模式串 | A | A | A | A | B |
next | -1 | 0 | 1 | 2 | 3 |
問題在于,當(dāng)
所以,我們要修正一下next數(shù)組。
大致流程和上面求解next數(shù)組時一樣,這里就是多了一個判別條件,如果在匹配時出現(xiàn)了
代碼如下:
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數(shù)組計算時是一樣的 # 可以得到nextval[j+1]=t+1 nextval_list[j] = t else: # Tj=Tt, 且T(j+1)==T(t+1), 這個就是next數(shù)組需要更新的 # nextval[j+1]=上一次的nextval_list[t] nextval_list[j] = nextval_list[t] else: # 匹配失敗, 模式串索引為t的字符與當(dāng)前位進(jìn)行比較 t = nextval_list[t] return nextval_list
對KMP的優(yōu)化其實就是對next數(shù)組的優(yōu)化,修正后的next數(shù)組,即nextval數(shù)組如下:
位編號 | 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}進(jìn)行匹配'.format(S1, T1)) print('{:*^25}'.format('KMP')) next_list1 = getNext(T1) print('next數(shù)組為: {}'.format(next_list1)) index1_1, count1_1 = KMP(S1, T1, next_list1) print('匹配到的位置(索引): {}, 匹配次數(shù): {}'.format(index1_1, count1_1)) print('{:*^25}'.format('KMP優(yōu)化版')) nextval_list1 = getNextval(T1) print('nextval數(shù)組為: {}'.format(nextval_list1)) index1_2, count1_2 = KMP(S1, T1, nextval_list1) print('匹配到的位置(索引): {}, 匹配次數(shù): {}'.format(index1_2, count1_2)) print('') print('*' * 50) print('主串S={0}與模式串T={1}進(jìn)行匹配'.format(S2, T2)) print('{:*^25}'.format('KMP')) next_list2 = getNext(T2) print('next數(shù)組為: {}'.format(next_list2)) index2_1, count2_1 = KMP(S2, T2, next_list2) print('匹配到的位置(索引): {}, 匹配次數(shù): {}'.format(index2_1, count2_1)) print('{:*^25}'.format('KMP優(yōu)化版')) nextval_list2 = getNextval(T2) print('nextval數(shù)組為: {}'.format(nextval_list2)) index2_2, count2_2 = KMP(S2, T2, nextval_list2) print('匹配到的位置(索引): {}, 匹配次數(shù): {}'.format(index2_2, count2_2))
運行結(jié)果如下:
運行的結(jié)果和我們分析的是一樣的,不修正next數(shù)組時,主串
結(jié)束語
在寫本篇博客之前也是反復(fù)看參考書、視頻,邊畫圖邊去理解它,這篇博客也是反復(fù)修改了好幾次,最終算是把KMP解決掉了,有關(guān)字符串知識的復(fù)習(xí)也算是基本結(jié)束,下面就是刷題了(雖然在LeetCode做過了幾道題)。
到此這篇關(guān)于Python描述數(shù)據(jù)結(jié)構(gòu)之KMP篇的文章就介紹到這了,更多相關(guān)Python KMP內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Python基礎(chǔ)之?dāng)?shù)據(jù)結(jié)構(gòu)詳解
- python中常用的數(shù)據(jù)結(jié)構(gòu)介紹
- python實現(xiàn)數(shù)據(jù)結(jié)構(gòu)中雙向循環(huán)鏈表操作的示例
- Python描述數(shù)據(jù)結(jié)構(gòu)學(xué)習(xí)之哈夫曼樹篇
- 基于python實現(xiàn)模擬數(shù)據(jù)結(jié)構(gòu)模型
- Python數(shù)據(jù)結(jié)構(gòu)dict常用操作代碼實例
- 基于Python數(shù)據(jù)結(jié)構(gòu)之遞歸與回溯搜索
- 淺析Python語言自帶的數(shù)據(jù)結(jié)構(gòu)有哪些
- Python 實現(xiàn)數(shù)據(jù)結(jié)構(gòu)-堆棧和隊列的操作方法
- Python 實現(xiàn)數(shù)據(jù)結(jié)構(gòu)-循環(huán)隊列的操作方法
- 4種非常實用的python內(nèi)置數(shù)據(jù)結(jié)構(gòu)
相關(guān)文章
Python matplotlib圖例放在外側(cè)保存時顯示不完整問題解決
這篇文章主要介紹了Python matplotlib圖例放在外側(cè)保存時顯示不完整問題解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07python讀取查看npz/npy文件數(shù)據(jù)以及數(shù)據(jù)完全顯示方法實例
前兩天從在GitHub下載了一個代碼,其中的數(shù)據(jù)集是.npz結(jié)尾的文件,之前沒有見過不知道如何處理,下面這篇文章主要給大家介紹了關(guān)于python讀取查看npz/npy文件數(shù)據(jù)以及數(shù)據(jù)完全顯示方法的相關(guān)資料,需要的朋友可以參考下2022-04-04Python中ArcPy柵格裁剪柵格(批量對齊柵格圖像范圍并統(tǒng)一行數(shù)與列數(shù))
本文介紹基于Python中ArcPy模塊,實現(xiàn)基于柵格圖像批量裁剪柵格圖像,同時對齊各個柵格圖像的空間范圍,統(tǒng)一其各自行數(shù)與列數(shù)的方法,本文通過實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02Python使用PyMySql增刪改查Mysql數(shù)據(jù)庫的實現(xiàn)
PyMysql是Python中用于連接MySQL數(shù)據(jù)庫的一個第三方庫,本文主要介紹了Python使用PyMySql增刪改查Mysql數(shù)據(jù)庫的實現(xiàn),具有一定的參考價值,感興趣的可以了解一下2024-01-01