ac自动机 匹配最长前缀_别再暴力匹配字符串了,高效的KMP,才是真的香
如果你想了解KMP算法,請(qǐng)靜下心讀完這篇文章,一定不會(huì)辜負(fù)你的時(shí)間
暴力匹配(BF)
字符串匹配是我們?cè)诰幊讨谐R?jiàn)的問(wèn)題,其中從一個(gè)字符串(主串)中檢測(cè)出另一個(gè)字符串(模式串)是一個(gè)非常經(jīng)典的問(wèn)題,當(dāng)提及到這個(gè)問(wèn)題時(shí)我們首先想到的算法可能就是暴力匹配,下面的動(dòng)圖就展示了暴力匹配的流程。
上圖中箭頭指向的字符都為藍(lán)色時(shí)代表二者匹配,都為黑色時(shí)代表二者不匹配,紅色則代表在主串中找到模式串。
這種算法大致思路就是每當(dāng)模式串和主串中有字符不匹配,模式串與主串對(duì)應(yīng)的位置整體向后移動(dòng)一位,再次從模式串第一位開(kāi)始比較,重復(fù)上述做法直至在主串中匹配到模式串或者匹配到主串最后一位結(jié)束。
如果主串與模式串都比較短時(shí),用暴力匹配還是不錯(cuò)的選擇,編碼也相對(duì)容易;但是如果主串與模式串過(guò)長(zhǎng)時(shí),我們只是簡(jiǎn)單想想就知道這個(gè)過(guò)程是非常耗時(shí)的,那么會(huì)不會(huì)有對(duì)應(yīng)的優(yōu)化算法呢?
下面就介紹本文的主角——KMP算法,不扯沒(méi)用的概念,直接講算法的應(yīng)用過(guò)程及利用Python實(shí)現(xiàn)該算法的代碼,最后會(huì)通過(guò)二者時(shí)間復(fù)雜度的分析,總結(jié)出為何KMP算法會(huì)優(yōu)于暴力匹配算法。
KMP算法
構(gòu)建前綴表
我們首先要確定一下引例的主串和模式串:
- 主 串 S = "abacaababc"
- 模式串P="ababc"
在模式串與主串匹配時(shí),我們暫時(shí)只看第4步,明顯主串S中的c和模式串P中的b是不匹配的:
如果用暴力匹配算法,那么就是后移模式串P,在從P的第一個(gè)字符開(kāi)始比較。但是現(xiàn)在通過(guò)匹配我們可以知道的是當(dāng)?shù)?位不匹配時(shí),前三個(gè)字符為"aba"是確定的,這個(gè)已知信息是十分有用的。
而KMP算法的核心就是利用匹配失敗后獲取的信息,盡量減少模式串與主串的匹配次數(shù)以達(dá)到快速匹配的目的,比如對(duì)于這個(gè)不匹配現(xiàn)象我們是不是可以直接這樣移動(dòng)模式串呢?
那么信息從何而來(lái)呢?在KMP算法中,對(duì)于一個(gè)模式串都可以先計(jì)算出其內(nèi)部的匹配信息,這樣在匹配失敗時(shí)可以最有效的移動(dòng)模式串,從而減少匹配次數(shù)。在此之前,需要先理解一下前綴和后綴。
- 前綴:abcde的前綴可以是a、ab、abc、abcd
- 后綴:abcde的后綴可以是e、de、cde、bcde
這里需要引出一個(gè)新的概念——前綴表,可以用profix表示、且下標(biāo)從0開(kāi)始,profix[i]存儲(chǔ)的信息就是前i+1個(gè)字符的最長(zhǎng)公共前后綴,并且這個(gè)最長(zhǎng)公共前后綴長(zhǎng)度一定是小于字符串長(zhǎng)度的。
可以看到"ababc"不是前后綴,但也被列到了表中。如果你曾經(jīng)了解過(guò)KMP算法,那你可能聽(tīng)過(guò)next數(shù)組,當(dāng)前綴表轉(zhuǎn)化為next數(shù)組時(shí),最后一位的值會(huì)被覆蓋掉,對(duì)過(guò)程是沒(méi)有什么影響的。由于本文僅是靠著前綴表profix完成KMP算法,所以不再過(guò)多講述next數(shù)組,不同的方法只是表示形式不一樣,但歸根結(jié)底原理還是相同的。
上面的前綴表是我們通過(guò)肉眼比對(duì)得出的,程序畢竟不是人嘛,所以需要通過(guò)一種程序能夠識(shí)別的方法構(gòu)建前綴表,依據(jù)下圖進(jìn)行講述流程。
通過(guò)這個(gè)動(dòng)圖可以將構(gòu)建前綴表規(guī)劃成下面五步:
當(dāng)結(jié)合動(dòng)圖讀完這五個(gè)步驟時(shí),我猜你會(huì)不理解第五步,如果你都理解了,我也只能感嘆一句NiuBi,利用下面這個(gè)例子更能凸顯出步驟五的回溯機(jī)制。
依據(jù)上面步驟我寫出了前綴表的前五位,而此時(shí)j和i指向的字符不匹配且j≠0,這里j的下標(biāo)是3,所以需要在前綴表中找到下標(biāo)為j-1的值,即profix[2],然后將j回溯到對(duì)應(yīng)的位置。
這樣回溯是因?yàn)榭梢栽谀J酱^部找到和j和i之間的字符串相匹配的前綴,也就是這個(gè)例子中的a,如果此時(shí)j和i指向的字符相匹配,那么最長(zhǎng)公共前后綴的長(zhǎng)度就是已匹配的前綴的長(zhǎng)度(a)再加1。由此可見(jiàn)如果j和i之間字符串很長(zhǎng)時(shí),這個(gè)操作可以節(jié)省很多時(shí)間。
而此時(shí)j和i指向的字符仍然不匹配,那么需要繼續(xù)回溯j,方法和上述一致,回溯的位置就是profix[0]。
此時(shí)j和i指向的字符還是不匹配,但這里需要做的就不是回溯了,因?yàn)閖=0已經(jīng)滿足回溯結(jié)束條件,只需將i對(duì)應(yīng)前綴表的位置(profix[5])中填入0即可,用肉眼匹配也會(huì)發(fā)現(xiàn)此時(shí)的確沒(méi)有公共前后綴。
在理解上述步驟之后,可以將其當(dāng)成偽代碼,依據(jù)偽代碼很容易編寫出構(gòu)建前綴表函數(shù)。
def PrefixTable(Pattern): i = 1;j = 0 prefix = [0]*len(Pattern) while(i可以輸入一個(gè)模式串,測(cè)試一下該代碼是否能夠得出對(duì)應(yīng)前綴表。
優(yōu)化前綴表
經(jīng)過(guò)上文解釋你可能會(huì)發(fā)現(xiàn)一個(gè)基本事實(shí),即前綴表最后一位沒(méi)有任何作用,這么說(shuō)的理由是什么呢?因?yàn)楫?dāng)j和i指向的字符不匹配時(shí),這里的解決辦法是回溯j,而回溯依據(jù)一直都是prefix[j-1],j是永遠(yuǎn)不可能超越i的,所以前綴表最后一位永遠(yuǎn)也不會(huì)用到。
那么最后一位就可以去掉,將所有元素整體后移一位,并向前綴表第一位填入-1,如下圖:
填入-1這個(gè)操作的原理等下結(jié)合圖片一起講述會(huì)更易懂,目前我們只需知道這個(gè)操作并且了解其對(duì)應(yīng)代碼即可。
-def MoveTable(prefix): for i in range(len(prefix)-1,0,-1): prefix[i] = prefix[i-1] prefix[0] = -1 return prefix復(fù)制代碼KMP匹配機(jī)制
主串和模式串還是利用上文所舉例子,這里省略了一些簡(jiǎn)單的匹配過(guò)程,直接看關(guān)鍵點(diǎn)。
可以看到主串和模式串的第4位是不匹配的,現(xiàn)在需要做的是將Pattern[prefix[4]]對(duì)應(yīng)主串中需要匹配的元素,也就是模式串下標(biāo)為1的元素后移至與主串第4位對(duì)應(yīng)的位置,看圖可懂。
對(duì)應(yīng)位置仍然不匹配,需要繼續(xù)后移模式串,該位置對(duì)應(yīng)前綴表的值為0,所以將Pattern[prefix[0]]對(duì)應(yīng)主串中需要匹配的元素,即模式串下標(biāo)為0的元素與主串該位置對(duì)應(yīng)。
此時(shí)兩串對(duì)應(yīng)位置還是不匹配,但是a已經(jīng)是模式串的第一位元素了,如果按照上面方法需要繼續(xù)后移模式串,讓主串那個(gè)位置與模式串下標(biāo)為-1的元素匹配,可是前綴表中并不存在下標(biāo)為-1的元素。
所以比較時(shí)如果模式串和主串對(duì)應(yīng)位置不匹配,且模式串的元素對(duì)應(yīng)前綴表的值為-1,那么直接將模式串整體后移一位,并且將指向主串的指針后移一位即可,這也是為什么在前綴表第一位插入-1的原因。
下面動(dòng)圖是利用KMP算法在主串中查找模式串的全過(guò)程。
KMP算法的代碼如下:
def KMP(TheString,Pattern): m = len(TheString);n = len(Pattern) prefix = PrefixTable(Pattern) prefix = MoveTable(prefix) i = 0;j = 0#i為主串指針,j為模式串指針 while(i這里只講一下第一個(gè)if語(yǔ)句,當(dāng)j指向了模式串最后一位,并且此時(shí)如果主串和模式串對(duì)應(yīng)位置匹配,則代表在主串中找到了模式串,并打印出第一個(gè)字符出現(xiàn)的位置。而j= prefix[j]這個(gè)語(yǔ)句的作用是在找到模式串后繼續(xù)匹配剩余的主串,因?yàn)榭赡軙?huì)有主串中含有若干個(gè)模式串的現(xiàn)象出現(xiàn)。
最后整個(gè)程序運(yùn)行截圖如下:
BF與KMP比較
為什么KMP會(huì)優(yōu)于BF,這里通過(guò)對(duì)比二者的時(shí)間復(fù)雜度給出原因,假設(shè)有這么兩個(gè)比較極端的主串和模式串:
- 主 串 S = "aaaaaaab"
- 模式串P="aaab"
首先看一下BF算法解決該匹配問(wèn)題的流程:
然后再看一下KMP算法解決該匹配問(wèn)題的流程:
假設(shè)主串長(zhǎng)度為m,模式串長(zhǎng)度為n。對(duì)于BF算法,每當(dāng)遇到不匹配字符時(shí),都要從模式串開(kāi)頭再次匹配,所以對(duì)應(yīng)時(shí)間復(fù)雜度O(m*n);對(duì)于KMP算法,每當(dāng)遇到不匹配字符時(shí),根據(jù)獲得的信息它不會(huì)重復(fù)匹配的已知前綴,所以對(duì)應(yīng)時(shí)間復(fù)雜度為O(m+n)。當(dāng)字符串較長(zhǎng)時(shí),就時(shí)間復(fù)雜度而言KMP算法是完全優(yōu)于BF算法的。
總結(jié)
個(gè)人認(rèn)為KMP算法難度不低,講這個(gè)算法的博客與視頻很多,但都各有差異,雖然原理都是大致相同的,但不要同時(shí)看前綴表和next數(shù)組,由于這兩個(gè)很像所以會(huì)容易混淆,可以先弄透前綴表然后再看next數(shù)組相關(guān)知識(shí)點(diǎn),這樣對(duì)于KMP的理解才算透徹。
作者:奶糖貓
鏈接:https://juejin.im/post/5eb8ac9d5188256d9147983e
來(lái)源:掘金
總結(jié)
以上是生活随笔為你收集整理的ac自动机 匹配最长前缀_别再暴力匹配字符串了,高效的KMP,才是真的香的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: java 优酷视频缩略图_优酷视频缩略图
- 下一篇: win10:tensorflow学习笔记