資料結構與演算法JavaScript(五) :串(經典KMP演算法)

發表於2015-01-16

KMP演算法和BM演算法

KMP是字首匹配和BM字尾匹配的經典演算法,看得出來字首匹配和字尾匹配的區別就僅僅在於比較的順序不同

字首匹配是指:模式串和母串的比較從左到右,模式串的移動也是從 左到右

字尾匹配是指:模式串和母串的的比較從右到左,模式串的移動從左到右。

通過上一章顯而易見BF演算法也是屬於字首的演算法,不過就非常霸蠻的逐個匹配的效率自然不用提了O(mn),網上蛋疼的KMP是講解很多,基本都是走的高大上路線看的你也是一頭霧水,我試圖用自己的理解用最接地氣的方式描述


KMP

KMP也是一種優化版的字首演算法,之所以叫KMP就是Knuth、Morris、Pratt三個人名的縮寫,對比下BF那麼KMP的演算法的優化點就在“每次往後移動的距離”它會動態的調整每次模式串的移動距離,BF是每次都+1,

KMP則不一定

如圖BF與KMP前置演算法的區別對比

我通過圖對比我們發現:

在文字串T中搜尋模式串P,在自然匹配第6個字母c的時候發現二等不一致了,那麼BF的方法,就是把整個模式串P移動一位,KMP則是移動二位.

BF的匹配方法我們是知道的,但是KMP為什麼會移動二位,而不是一位或者三位四位呢?

這就上一張圖我們講解下,模式串P在匹配了ababa的時候都是正確的,當到c的時候才是錯誤,那麼KMP演算法的想法是:ababa是正確的匹配完成的資訊,我們能不能利用這個資訊,不要把”搜尋位置”移回已經比較過的位置,繼續把它向後移,這樣就提高了效率。

那麼問題來了, 我怎麼知道要移動多少個位置?

這個偏移的演算法KMP的作者們就給我們總結好了:

移動位數 = 已匹配的字元數 – 對應的部分匹配值

偏移演算法只跟子串有關係,沒文字串沒毛線關係,所以這裡需要特別注意了

那麼我們怎麼理解子串中已匹配的字元數與對應的部分匹配值?


已匹配的字元:

T : abababaabab

p : ababacb

p中紅色的標記就是已經匹配的字元,這個很好理解


部分匹配值:

這個就是核心的演算法了,也是比較難於理解的

假如:

我們可以觀察這個文字如果我們在匹配c的時候出錯,我們下一個移動的位置就上個的結構來講,移動到那裡最合理?

那麼就是說:在模式文字內部,某一段字元頭尾都一樣,那麼自然過濾的時候可以跳過這一段內容了,這個思路也是合理的

知道了這個規律,那麼給出來的部分匹配表演算法如下:

首先,要了解兩個概念:”字首”和”字尾”。 “字首”指除了最後一個字元以外,一個字串的全部頭部組合;”字尾”指除了第一個字元以外,一個字串的全部尾部組合。

“部分匹配值”就是”字首”和”字尾”的最長的共有元素的長度”

我們看看aaronaac的如果是BF匹配的時候劃分是這樣的

BF的位移: a,aa,aar,aaro,aaron,aarona,aaronaa,aaronaac

那麼KMP的劃分呢?這裡就要引入字首與字尾了

我們先看看KMP部分匹配表的結果是這樣的:

肯定是一頭霧水,不急我們分解下,字首與字尾

移動的位置:其實就是針對每一個已匹配的字元做字首與字尾的對比是否相等,然後算出共有的長度


部分匹配表的分解

KMP中的匹配表的演算法,其中p表示字首,n表示字尾,r表示結果

類似BF演算法一下,先分解每一次可能匹配的下標的位置先快取起來,在匹配的時候通過這個《部分匹配表》來定位需要後移動的位數

所以最後aaronaac的匹配表的結果 0,1,0,0,0,1,2,0 就是這麼來的

下面將會實現JS版的KMP,有2種

KMP實現(一):快取匹配表的KMP

KMP實現(二):動態計算next的KMP


KMP實現(一)

匹配表

KMP演算法中最重要的就是匹配表,如果不要匹配表那就是BF的實現,加上匹配表就是KMP了

匹配表決定了next下一個位移的計數

針對上面匹配表的規律,我們設計一個kmpGetStrPartMatchValue的方法

完全按照KMP中的匹配表的演算法的實現,通過str.substring(0, i + 1) 分解a->aa->aar->aaro->aaron->aarona->aaronaa-aaronaac

然後在每一個分解中通過字首字尾算出共有元素的長度

回退演算法

KMP也是前置演算法,完全可以把BF那一套搬過來,唯一修改的地方就是BF回溯的時候直接是加1,KMP在回溯的時候我們就通過匹配表算出這個next值即可

紅色標記的就是KMP的核心點 next的值  = 已匹配的字元數 – 對應的部分匹配值

完整的KMP演算法


KMP(二)

第一種kmp的演算法很明顯,是通過快取查詢匹配表也就是常見的空間換時間了。那麼另一種就是時時查詢的演算法,通過傳遞一個具體的完成字串,算出這個匹配值出來,原理都一樣

生成快取表的時候是整體全部算出來的,我們現在等於只要挑其中的一條就可以了,那麼只要演算法定位到當然的匹配即可

next演算法

其實跟匹配表是一樣的,去掉了迴圈直接定位到當前已成功匹配的串了

完整的KMP.next演算法

git程式碼下載: https://github.com/JsAaron/data_structure

相關文章