淺談Python描述數(shù)據(jù)結(jié)構(gòu)之KMP篇
前言
本篇章主要介紹串的KMP模式匹配算法及其改進(jìn),并用Python實(shí)現(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算法,是由
就是這次匹配失敗時(shí),下次匹配時(shí)模式串應(yīng)該從哪一位開始比較。
BF算法思路簡(jiǎn)單,便于理解,但是在執(zhí)行時(shí)效率太低。在上述的匹配過程中,第一次匹配時(shí)已經(jīng)匹配的
前綴:是指除最后一個(gè)字符外,字符串的所有頭部子串。
后綴:是指除第一個(gè)字符外,字符串的所有尾部子串。
部分匹配值
例如,
前綴一定包含第一個(gè)字符,后綴一定包含最后一個(gè)字符。
如果模式串1號(hào)位與主串當(dāng)前位(箭頭所指的位置)不匹配,將模式串1號(hào)位與主串的下一位進(jìn)行比較。next[0]=-1,這邊就是一個(gè)特殊位置了,即如果主串與模式串的第1位不相同,那么下次就直接比較各第2位的字符。
如果模式串2號(hào)位與主串當(dāng)前位不匹配,找最長(zhǎng)公共前后綴,指針前面的子串為

如果模式串3號(hào)位與主串當(dāng)前位不匹配,找最長(zhǎng)公共前后綴,指針前面的子串為
如果模式串4號(hào)位與主串當(dāng)前位不匹配,找最長(zhǎng)公共前后綴,指針前面的子串為

如果模式串5號(hào)位與主串當(dāng)前位不匹配,找最長(zhǎng)公共前后綴,指針前面的子串為

如果模式串6號(hào)位與主串當(dāng)前位不匹配,找最長(zhǎng)公共前后綴,指針前面的子串為

如果模式串7號(hào)位與主串當(dāng)前位不匹配,找最長(zhǎng)公共前后綴,指針前面的子串為
如果模式串8號(hào)位與主串當(dāng)前位不匹配,找最長(zhǎng)公共前后綴,指針前面的子串為
綜上,可以得到模式串的next數(shù)組,發(fā)現(xiàn)沒有,把主串去掉也可以得到這個(gè)數(shù)組,即下次匹配時(shí)模式串向后移動(dòng)的位數(shù)與主串無關(guān),僅與模式串本身有關(guān)。
| 位編號(hào) | 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ù)組,即存放的是每個(gè)字符匹配失敗時(shí),對(duì)應(yīng)的下一次匹配時(shí)模式串開始匹配的位置。
如何在代碼里實(shí)現(xiàn)上述流程呢?舉個(gè)栗子,藍(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目的就是第一位匹配失敗時(shí)
# 主串位置加1, 匹配串回到第一個(gè)位置(索引為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)沒有,在第一個(gè)圖中,我們還可以跳過第3次匹配,直接進(jìn)行第4次匹配。為了更好地說明問題,我們以下面這種情況為例,來優(yōu)化一下KMP算法。假設(shè)主串

可以看到第2、3、4次的匹配是多余的,因?yàn)槲覀冊(cè)诘谝淮纹ヅ鋾r(shí),主串
那么,問題出在哪里???我們結(jié)合著next數(shù)組看一下:
| 位編號(hào) | 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ù)組時(shí)一樣,這里就是多了一個(gè)判別條件,如果在匹配時(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), 這個(gè)就和next數(shù)組計(jì)算時(shí)是一樣的
# 可以得到nextval[j+1]=t+1
nextval_list[j] = t
else:
# Tj=Tt, 且T(j+1)==T(t+1), 這個(gè)就是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
對(duì)KMP的優(yōu)化其實(shí)就是對(duì)next數(shù)組的優(yōu)化,修正后的next數(shù)組,即nextval數(shù)組如下:
| 位編號(hào) | 1 | 2 | 3 | 4 | 5 |
|---|---|---|---|---|---|
| 索引 | 0 | 1 | 2 | 3 | 4 |
| 模式串 | A | A | A | A | B |
| nextval | -1 | -1 | -1 | -1 | 3 |
下面就測(cè)試一下:
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))
運(yùn)行結(jié)果如下:

運(yùn)行的結(jié)果和我們分析的是一樣的,不修正next數(shù)組時(shí),主串
結(jié)束語
在寫本篇博客之前也是反復(fù)看參考書、視頻,邊畫圖邊去理解它,這篇博客也是反復(fù)修改了好幾次,最終算是把KMP解決掉了,有關(guān)字符串知識(shí)的復(fù)習(xí)也算是基本結(jié)束,下面就是刷題了(雖然在LeetCode做過了幾道題)。
到此這篇關(guān)于Python描述數(shù)據(jù)結(jié)構(gòu)之KMP篇的文章就介紹到這了,更多相關(guān)Python KMP內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Python基礎(chǔ)之?dāng)?shù)據(jù)結(jié)構(gòu)詳解
- python中常用的數(shù)據(jù)結(jié)構(gòu)介紹
- python實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)中雙向循環(huán)鏈表操作的示例
- Python描述數(shù)據(jù)結(jié)構(gòu)學(xué)習(xí)之哈夫曼樹篇
- 基于python實(shí)現(xiàn)模擬數(shù)據(jù)結(jié)構(gòu)模型
- Python數(shù)據(jù)結(jié)構(gòu)dict常用操作代碼實(shí)例
- 基于Python數(shù)據(jù)結(jié)構(gòu)之遞歸與回溯搜索
- 淺析Python語言自帶的數(shù)據(jù)結(jié)構(gòu)有哪些
- Python 實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)-堆棧和隊(duì)列的操作方法
- Python 實(shí)現(xiàn)數(shù)據(jù)結(jié)構(gòu)-循環(huán)隊(duì)列的操作方法
- 4種非常實(shí)用的python內(nèi)置數(shù)據(jù)結(jié)構(gòu)
相關(guān)文章
PyCharm最新激活碼(2020/10/27全網(wǎng)最新)
Pycharm最新激活碼全網(wǎng)最新(2020/10/27更新),適用Intellij idea 2020.2.x,WebStorm 2020.2.x,Pycharm 2020.2.x2020-10-10
使用python對(duì)pdf文件進(jìn)行加密等操作
這篇文章主要為大家詳細(xì)介紹了使用python對(duì)pdf文件進(jìn)行加密等操作的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-12-12
使用Python實(shí)現(xiàn)合并多個(gè)Excel文件
合并Excel可以將多個(gè)文件中的數(shù)據(jù)合并到一個(gè)文件中,這樣可以幫助我們更好地匯總和管理數(shù)據(jù),本文主要介紹了如何使用第三方Python庫(kù) Spire.XLS for Python 實(shí)現(xiàn)以上兩種合并Excel文件的需求,有需要的可以了解下2023-12-12
Python實(shí)現(xiàn)端口流量轉(zhuǎn)發(fā)的示例代碼
端口流量轉(zhuǎn)發(fā)(Port Forwarding)是一種網(wǎng)絡(luò)通信技術(shù),用于將特定的網(wǎng)絡(luò)流量從一個(gè)端口或網(wǎng)絡(luò)地址轉(zhuǎn)發(fā)到另一個(gè)端口或地址,它在網(wǎng)絡(luò)中扮演著一個(gè)非常重要的角色,在Python語言中實(shí)現(xiàn)端口轉(zhuǎn)發(fā)非常容易,文中有相關(guān)的代碼示例,需要的朋友可以參考下2023-11-11
使用Python神器對(duì)付12306變態(tài)驗(yàn)證碼
這篇文章主要介紹了使用Python神器對(duì)付12306變態(tài)驗(yàn)證碼的相關(guān)資料,需要的朋友可以參考下2016-01-01
在Python中字典根據(jù)多項(xiàng)規(guī)則排序的方法
今天小編就為大家分享一篇在Python中字典根據(jù)多項(xiàng)規(guī)則排序的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2019-01-01
Python中OpenCV實(shí)現(xiàn)簡(jiǎn)單車牌字符切割
本文將結(jié)合實(shí)例代碼,在Jupyter Notebook上使用Python+opencv實(shí)現(xiàn)如下簡(jiǎn)單車牌字符切割。感興趣的小伙伴可以參考一下2021-06-06

