1. 程式人生 > >字串匹配shiftand演算法//(轉載的,但沒有找到最終出處)

字串匹配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思路簡單很多。它主要是維護了一個集合,該集合中儲存的是所有既是模式串的字首同時又是目標串的字尾的字串。每次讀入一個新的文字,本演算法就利用位並行的方法更新該集合(神奇之處)。該集合用一個位掩碼D進行表示:dm...d1表示(m表示的是模式串的大小)。

第二步:演算法介紹(可以先看第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!!

--------------------- 本文來自 silence401 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/silence401/article/details/77446537?utm_source=copy