日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

KMP 快速匹配字符串 算法 数据结构

發(fā)布時(shí)間:2024/1/1 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 KMP 快速匹配字符串 算法 数据结构 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

以下內(nèi)容轉(zhuǎn)載自?https://www.toutiao.com/i6854730621588242951/

今天是算法數(shù)據(jù)結(jié)構(gòu)專題的第29篇文章,我們來(lái)聊一個(gè)新的字符串匹配算法——KMP。

?

KMP這個(gè)名字不是視頻播放器,更不是看毛片,它其實(shí)是由Knuth、Morris、Pratt這三個(gè)大牛名字的合稱。老外很喜歡用人名來(lái)命名算法或者是定理,數(shù)學(xué)里就有一堆,什么高斯定理、歐拉函數(shù)什么的。但是中國(guó)人更傾向于從表意上來(lái)給一個(gè)概念命名,比如勾股定理、同余定理等等。之前覺(jué)得用人名命名很洋氣,作者可以青史留名,后來(lái)想想這也是英文表意能力不足,很難用表意的方式起名的體現(xiàn)。

?

扯遠(yuǎn)了,我們回到正題。

?

應(yīng)用場(chǎng)景

?

在計(jì)算機(jī)領(lǐng)域當(dāng)中字符串匹配其實(shí)是一個(gè)非常常見(jiàn)的問(wèn)題,我們使用它的場(chǎng)景也多到不可計(jì)數(shù)。比如在一個(gè)已經(jīng)打開(kāi)的頁(yè)面當(dāng)中搜索關(guān)鍵詞,再比如說(shuō)git里面的代碼變動(dòng)的記錄,以及論文的查重等等。在這些問(wèn)題當(dāng)中有些情況可能還好,比如說(shuō)我們搜索一個(gè)關(guān)鍵詞,因?yàn)殛P(guān)鍵詞并不長(zhǎng),我們暴力枚舉也不會(huì)特別耗時(shí)。但是在有些問(wèn)題當(dāng)中明顯暴力匹配是無(wú)法勝任的,比如論文查重。一篇論文動(dòng)輒上千詞,要和庫(kù)中的上萬(wàn)篇文章進(jìn)行查重掃描,這當(dāng)中的工作量可想而知。如果是暴力枚舉算法那查重顯然會(huì)查到天荒地老。

?

所以早期的時(shí)候字符串匹配是一個(gè)難題,既然是難題那么顯然就會(huì)有很多人來(lái)研究,也因此出了很多成果,很多大牛發(fā)表了字符串匹配的算法,其中KMP算法由于效率很高、實(shí)現(xiàn)復(fù)雜度低被應(yīng)用得最廣。到這里,我們就知道KMP算法是用來(lái)字符串匹配的。

?

比方說(shuō)我們有兩個(gè)字符串,A串是:I hate learning English. B串是hate learning,很明顯B串是A串的字符串。如果我們暴力枚舉來(lái)判斷的話,我們需要遍歷A串當(dāng)中的每一個(gè)起始位置是否能夠完成匹配,那么復(fù)雜度顯然是O(mn)。通過(guò)KMP算法,我們可以在O(n)的時(shí)間內(nèi)做到這點(diǎn)。

?

著名的大佬matrix67在KMP算法的介紹博客當(dāng)中有一句著名的騷話,當(dāng)你有一個(gè)喜歡的MM,你可以委婉地問(wèn)她:“假如你要向你喜歡的人表白的話,我的名字是你的告白語(yǔ)中的子串嗎?”

?

Next數(shù)組

?

KMP算法的核心精髓只有一個(gè)就是Next數(shù)組,但是這個(gè)概念并不太容易理解,很多人學(xué)KMP放棄就是折戟在了Next這個(gè)數(shù)組上。

?

我們先把Next數(shù)組是怎么來(lái)的放在一邊,先來(lái)看下Next數(shù)組是用來(lái)干嘛的,它起作用的原理是什么,最后再來(lái)討論Next數(shù)組怎么來(lái)的問(wèn)題。根據(jù)我的理解,Next數(shù)組其實(shí)就是一個(gè)中途開(kāi)始的機(jī)會(huì),也就是當(dāng)我們?cè)诿杜e匹配的時(shí)候,發(fā)現(xiàn)了不匹配的情況,我們不是從頭開(kāi)始,而是從一個(gè)最大可能的中間結(jié)果開(kāi)始。

?

我們來(lái)看個(gè)例子:

?

上圖中上面的是A串,下面的是B串,我們?cè)谄ヅ涞倪^(guò)程當(dāng)中發(fā)現(xiàn)B串的前面幾位都匹配上了,而在最后一位匹配失敗。按照常規(guī)的做法,我們應(yīng)該是移動(dòng)到下一個(gè)位置從頭開(kāi)始匹配。但是這是非常浪費(fèi)的,因?yàn)槲覀冇^察下可以發(fā)現(xiàn)失敗位置的ABC和B串開(kāi)頭的ABC是可以構(gòu)成匹配的

?

我們之前失敗的時(shí)候判斷的是以C結(jié)尾的ABCDABC和B串的匹配,在這一次匹配失敗之后,我們可以繼續(xù)嘗試匹配其他以C結(jié)尾的前綴串,比如ABC。這樣我們就可以從中間狀態(tài)開(kāi)始,而節(jié)省了許多次不必要的枚舉。但問(wèn)題就來(lái)了,這個(gè)中間結(jié)果是怎么來(lái)的呢,我們?cè)趺粗喇?dāng)下失敗了上一個(gè)可行的中間結(jié)果是哪一個(gè)?

?

對(duì),沒(méi)有錯(cuò),前面說(shuō)到的Next數(shù)組就是用來(lái)存儲(chǔ)中間結(jié)果的。所以Next可以理解成下一次機(jī)會(huì)的意思,這樣就好理解了。由于我們是在A串當(dāng)中尋找B串,所以這個(gè)Next數(shù)組應(yīng)該是針對(duì)B的,記錄B中每一個(gè)位置如果匹配失敗,它的前面一個(gè)可行的中間狀態(tài)是哪一個(gè)。

?

我們先寫(xiě)出來(lái)B的Next數(shù)組,等會(huì)再去研究它是怎么得到的。為了簡(jiǎn)化編碼,我們假設(shè)字符串是從1位置開(kāi)始的,所以我們?cè)?的位置添加一個(gè)$符號(hào)作為占位符。對(duì)于大部分情況都是沒(méi)有重來(lái)的機(jī)會(huì)的,失敗了直接歸零。而其中的A和B兩個(gè)位置是有重來(lái)機(jī)會(huì)的,因?yàn)锽的前綴當(dāng)中出現(xiàn)了A和AB。所以如果在匹配ABD的時(shí)候失敗了,我們還可以從AB處再次開(kāi)始嘗試匹配ABC。

?

算法原理

?

我們想象一根指針指向了B數(shù)組當(dāng)中接下來(lái)要匹配的位置,如果匹配失敗了,它就會(huì)跳轉(zhuǎn)到Next數(shù)組當(dāng)中記錄的位置去,匹配成功了我們就向后移動(dòng)一位。在有了Next數(shù)組之后,我們寫(xiě)出代碼來(lái)真的很容易了:

?

def kmp(var_str, template_str):# var_str即A串# template_str模式串即B串# 我們?cè)趦蓚€(gè)字符串前加上了占位符var_str = '$' + var_strtemplate_str = '$' + template_strnext = generate_next(template_str)n, m = len(var_str), len(template_str)# head指向要匹配的位置的前一位head = 0for i in range(1, n):# 由于next數(shù)組很長(zhǎng),可能失敗多次# 直到head+1的位置能匹配上或者h(yuǎn)ead等于0while head > 0 and template_str[head+1] != var_str[i]:head = next[head]# 匹配上了則head變長(zhǎng)一位if template_str[head+1] == var_str[i]:head += 1# 如果head長(zhǎng)度等于B串了,則表示匹配成功if head == m - 1:return Truereturn False

?

對(duì)于A串中的每一個(gè)位置來(lái)說(shuō),我們都在B串當(dāng)中遍歷了每一個(gè)有可能構(gòu)成匹配的前綴。所以說(shuō)這個(gè)算法是可行的,一定可以獲得解。另外一個(gè)問(wèn)題是復(fù)雜度的問(wèn)題,為什么我們用了兩重循環(huán),但仍然是O(n)的算法呢?

?

其實(shí)很簡(jiǎn)單,因?yàn)閣hile循環(huán)只會(huì)讓head減小,而不會(huì)讓head增加。head增加是在for循環(huán)里執(zhí)行的,也就是說(shuō)head最多增加n次。那么對(duì)應(yīng)的while循環(huán)也就最多執(zhí)行n次,因?yàn)閔ead是非負(fù)的。所以while循環(huán)在整個(gè)for循環(huán)執(zhí)行的過(guò)程當(dāng)中最多執(zhí)行了n次,整體執(zhí)行的次數(shù)仍然是O(n)級(jí)別的而不是n^2級(jí),當(dāng)然是線性的算法。

?

求解Next

?

到這里,問(wèn)題只剩下了一個(gè),就是這個(gè)Next怎么來(lái)呢?

?

其實(shí)我們?cè)谥爸vNext數(shù)組的使用的時(shí)候已經(jīng)泄露天機(jī)了,我們?cè)賮?lái)看下上圖,不知道大家能感覺(jué)到什么。

?

后面一個(gè)A的Next值是1,也就是第一個(gè)A的下標(biāo),后面一個(gè)B的Next值是2,也就是第一個(gè)B的下標(biāo)。換句話說(shuō)第二個(gè)A能夠和位置1的A匹配,后面的AB能和前綴的AB匹配。也就是說(shuō)Next數(shù)組其實(shí)就是B數(shù)組自己和自己匹配的結(jié)果,我們?cè)谝婚_(kāi)始的時(shí)候?qū)⒄麄€(gè)Next數(shù)組全部置為0,然后依次遞推迭代出所有的Next的值。

?

我們在求解Next[i]的時(shí)候我們可以利用上Next[i-1]的值,因?yàn)镹ext[i-1]存儲(chǔ)的是能夠與B[i-1]匹配的前綴的結(jié)尾位置。如果B[Next[i-1]+1]等于B[i],那么說(shuō)明Next[i] = Next[i-1] + 1。如果不等的話,我們可以用while循環(huán)來(lái)尋找能夠匹配上的前綴。也就是說(shuō)這是一個(gè)遞推的過(guò)程,不過(guò)要注意一點(diǎn)我們計(jì)算Next數(shù)組要從2開(kāi)始,因?yàn)閷?duì)于1來(lái)說(shuō),Next[1]一定等于0。

?

def generate_next(var_str):n = len(var_str)next = [0 for _ in range(n)]for i in range(2, n):# 用next[i-1]作為開(kāi)始尋找能夠匹配上的最長(zhǎng)next[i]head = next[i-1]while head > 0 and var_str[head+1] != var_str[i]:head = next[head]# 如果匹配上了,head+1if var_str[head+1] == var_str[i]:head = head + 1# 記錄下來(lái)next[i] = headreturn next

?

總結(jié)

?

到這里,我們關(guān)于KMP算法的介紹就結(jié)束了,不知道大家看完之后感受如何,是不是有點(diǎn)蒙圈呢?

?

其實(shí)蒙圈是正常的,我第一次學(xué)的時(shí)候足足看了好幾遍才算是看明白。這畢竟是一個(gè)比較巧妙的算法,想要通過(guò)閱讀一篇文章就完全學(xué)會(huì)還是比較困難的,最好的還是親自動(dòng)手實(shí)現(xiàn)一下試試。KMP算法我最大的感受就是如果你把整個(gè)算法的邏輯都串起來(lái)了,那么即是自己從頭到尾推導(dǎo)一遍難度也不是很大(我就在面試當(dāng)中推導(dǎo)過(guò)一次)。如果你沒(méi)能把邏輯串起來(lái),那么覺(jué)得難理解看不懂是正常的,你可能需要再讀一遍或者是尋找一些其他的資料查漏補(bǔ)缺。

?

今天的文章到這里就結(jié)束了,如果喜歡本文的話,請(qǐng)來(lái)一波素質(zhì)三連,給我一點(diǎn)支持吧(關(guān)注、轉(zhuǎn)發(fā)、點(diǎn)贊)。

?

本文始發(fā)于公眾號(hào):TechFlow

總結(jié)

以上是生活随笔為你收集整理的KMP 快速匹配字符串 算法 数据结构的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。