字串匹配shiftand演算法
令人驚歎的Shift-And/Shift-Or
寫在前面:Shift-And/Shift-Or是如此令人驚歎的演算法,在KMP基礎上開始一段神奇之旅。
目的:以Shift-And演算法為載體,試圖在減少思維斷層情況下學習作者演算法思想。
目錄:
1:主要思想
2:演算法介紹
3:構建輔助表B
4:容器建立和更新
5:過程展示
6: Shift-And VS KMP,展示Shift-And令人驚歎之處
7:在KMP的基礎上,揭示Shift-And的神器:位並行(精髓)
第一步:主要思想(可以先看第3-5步,更容易理解)
Shift-And演算法和KMP演算法一樣,是基於字首來進行字串匹配。但是它的演算法思想要比KMP思路簡單很多。
第二步:演算法介紹(可以先看第3-5步,更容易理解)
D的第j位為1的時候(此時成Dj是活動的),表示模式串字首的p1…pj同時也是目標串t1…ti字尾.而當dm是1的時候,表示有一個匹配成功。當讀入目標串的下一個字元t(i+1)的時候,需要對D進行更新為D’當且僅當D的第j位是活動的,並且t(i+1)和p(j+1)相同的時候,此時可以利用位並行的方法在常數時間內對D進行更新。
第三步:構建輔助表B
集合B記錄模式串中每一個字元位掩碼bm…b1.如果pj=x,則B[x]的第j為設為1.否則為0.
舉例1:模式串announce共8位
同理可以得到B(其中模式串中不包含的字元*設為00000000)
第四步:容器建立和更新
對於容器D,初始化為00000000(0m :前m位全為0).表示當前還沒有即使模式串字首又是目標串字尾。
當讀入一個新的目標串字元t(i+1),可以以如下公式進行更新。
第五步:過程展示
下面是整個過程:目標串是’annual_announce’ 模式串announce
其實這個例子個人覺得並不是很理想,雖然它能說明情況,但是很難從這個例子的過程中體會到這個過程奇妙發生的根本所在。
例子2:
目標串是cbcbcbaefd 模式串是cbcba
建表B:
如果你看明白了,就會發現上述做法真的很奇妙。
第5)步中,讀取了c,但是模式串中的確實a,在沒有讀取c之前,結果是01010,這個的意思是模式串前4個字元前兩個字元cb和前4個字元cbcb既是模式串的字首,同時又是目標串的字尾。當讀入第5個字元c後,經過更新D後變成了00101,這個結果表示前5個字元中,只有第1個字元c和前3個字元cbc既是模式串的字首又是目標串的字尾。
這就是它的厲害之處,讀入一個新的字元之後,經過這樣3個步驟,就計算出來當前模式串前5個字元中所有的字首(同時是目標串的字尾)。
也許這樣還表現不太明顯,但是如果你很熟悉KMP演算法,因為KMP的貢獻在於它並不進行回溯同時很巧妙的利用迭代改變指標j。如果說kmp巧妙,它確實是,但是和Shift-And相比,真是小巫見大巫。因為kmp是用迭代,有可能需要迭代很多次,才能達到效果,此處只是一次位並行操作,就達到了kmp的效果。效率大大的提高。
第六步:Shift-And VS KMP,展示Shift-And令人驚歎之處
KMP演算法的精髓在於不回溯並採用巧妙的迭代方法得到next陣列,將時間複雜度理論上降到了o(n)。
如果想清楚瞭解KMP,可以參考
以下面這個案例再次進行分析:
當第26個字元,c和f匹配失敗的時候,kmp使用的方法是:找到了模式串中前25個字元的所有的既是模式串的字首同時又是目標串的字尾。
找到了這4對字首 a,aca,acabaca,acabacabaca.
上圖中第1步:由於最大的字首acabacabaca的後面一個字元t和第26個字元c並不匹配,執行第2步。
上圖中第2步:由於第二大的字首acabaca(同時是最大字首acabacabaca的最大字首,這是kmp演算法實施迭代技巧的的根本性質)後面的一個字元b和第26個字元c並不匹配,執行第3步。
上圖中第3步同樣面臨這c和b不等的情況,執行第4步。
上圖中第4步:由於字首a後一個字元c和第26個字元相同,此時指向目標串的指標i= 26和執行模式串的指標j由原來的26經過一系列改變(12,8,2)最終為2. 然後i++和j++開始匹配下一個字元.
程式碼:
(本段程式碼在上篇文章中有)
然而在看一下shift-and演算法是如何找到這個kmp進行了4次迭代才找到的第2位的c的。
此例中:
B[c] =
當比較完第25個字元之後
D =
(這個是根據D的定義結合上述圖片展示的4對字首寫出來的)
運算過程:
只是一次運算就計算出了kmp中需要4次迭代才能計算出來的結果。
那麼Shift-And是如何需要1次就做到的KMP 演算法4次才能做到的效果呢?下面來演示這個過程。
第七步:在KMP的基礎上,揭示Shift-And的神器:位並行(精髓)
再來看張圖:其實這4步操作,目的只有一個,就是拿目標串的c和模式串的4對字首的後一個字元相比較(其他字元都不需要比較)。即c和t,b,b,c相比較。
而t,b,b,c的位置是什麼?12,8,4,2。
再看看Shift-And中的D左移一位之後是什麼呢?
你可以清楚的看到上面的數字,奇妙之處就在這裡。
(注:26實際上已經比較過了,就是第1次比較c和f)
這個只是找到了要和c比較的位置,下一步就是比較這些位置是不是c,所以才有了shift-And演算法中第三步:相與操作。即從容器B中提取出來c出現的所有的位置即位掩碼B[c]
其實這裡為什麼能用相與操作呢?如果想要深入理解相與的妙處,可以先看一個簡單的案例
走到這一步完了嗎?當然還沒有,不過已經接近重點了。那就是Shift-And的第2步?加1是為了什麼?
其實這個沒有什麼神祕之處,只是如果你對kmp不是特別熟,即便是很熟悉也有可能會忘記。就是在這幅圖中
我們幸運的是第4步發現了相同的c,但是如果沒有發現呢?例如目標串的第26個字元不是c而是a呢?我們就會有第5步,和它比較的是誰呢?是第1個字元。這個意思是第4步失敗,將要尋找c前面即a的最大字首再加1的位置(和前3次一樣)而我們預設a的最大字首+1等於0+1=1.也就是很多其他部落格中引用原作者說的那句話:空字串也是目標串的字尾。a的字首還有一個空字元。實際上本例也能看出來,第目標串第26個字元是a,顯然有和模式串第一個字元a比較的需要。
現在如果看懂了整個過程,可以去分析第一步和第二步所說是如此經典。
不得不說,Shift-And演算法是看透了KMP基於字首匹配的本質特徵,即比較的時候實際上是非黑即白的比較,使用01這種方法實在令人佩服。這個演算法效率一般是KMP的2倍以上。當然,基於位並行操作實際和機器字長有關係,比如32位限制或者其他,但是它在絕大部分機器上都能執行,除非機器字長為8(這種機器應該年齡很大了吧..),你所查詢的是大於8位。
至於Shift-Or,它所用的原理是一樣的,只不過更加富有技巧性,使用了反碼和取反等操作,加速D’的計算。有興趣可以自行研究。如果看懂本例,在用點心相信看懂Shift-Or沒什麼問題。
感謝Shift-And演算法帶我們走了一段神奇之旅!!!Wonderful!!