1. 程式人生 > >字串匹配基礎上

字串匹配基礎上

單模式匹配演算法,也就是一個字串和另一個字串進行匹配。

1. BF 演算法

BF 演算法中的 BF 是 Brute Force 的縮寫,中文叫作暴力匹配演算法,也加樸素匹配演算法。從名字可以看出,這種方法很暴力,效率也不高,但是簡單、好懂。

在要匹配的兩個字串中,一個稱之為主串,一個稱之為模式串。比如要在字串中 A 中查詢字串 B,那麼字串 A 就是主串,字串 B 就是模式串。子串的長度為 n,模式串的長度為 m,因為要在主串中查詢模式串,所以有 n>m。

BF 演算法的思想很簡單,就是拿主串中起始位置分別為 \(0, 1,\cdots n-m\) 長度為 m 的總共 n-m+1 個子串分別與模式串進行比較,看有沒有能匹配上的

可以看到,每次我們都要比較 m 個字元,極端情況下總共要比較 n-m+1 次,所以演算法的最壞情況時間複雜度為 O(m*n)。

可以看到,這個演算法的時間複雜度很高,但在實際的開發中,它卻是一個比較常用的字串匹配演算法。一者因為實際開發中兩個字串的長度都不會太長,而且也不會每次都需要比較 n-m+1 次;二者因為其演算法實現起來非常簡單,不容易出錯,便於維護。這也就是我們常說的 KISS(Keep it Simple and Stupid) 原則。

2. RK 演算法

RK 演算法的全稱叫作 Rabin-Karp 演算法,是為了紀念它的兩個發明者而這樣命名的。 這個演算法其實就是剛剛 BF 演算法的升級版。

在 BF 演算法中,每次都要對 m 個字元逐個進行比較,這就大大降低了演算法的效率。這時候,我們引入雜湊演算法,對子串逐個求雜湊值,然後與模式串的雜湊值進行比較來判斷兩個子串是否匹配。在不考慮雜湊衝突的情況下,數字之間的比較就非常快了。

但是,在計運算元串雜湊值的時候,我們依然需要遍歷 m 個字元,演算法整體的效率並沒有提高。我們需要設計一個特殊的雜湊函式來避免每次都要遍歷 m 個字元,這樣,演算法的效率就會大大改善。

對此,我們將包含 K 個字元的子串用一個 K 進位制數來表示,將這個 K 進位制數轉化為 10 進位制數作為子串的雜湊值。比如字串都是由小寫字母組成,那麼 a-z 這 26 個字母就對映到 0-25,0 表示 a,1 表示 b,以此類推。將 K 進位制的數轉化為 10 進位制只需要將 10 變為 K 即可,如下圖所示。

這樣計算雜湊值的話相鄰兩個子串就有一定關係。

假設 \(S[i]\)\(S[i-1]\) 分別是起始位置為 \(i\)\(i-1\) 的雜湊值,而 \(h[i]\) 為位置為 \(i\) 處字元的的對映,那麼就有:

\[S[i] = 26*(S[i-1]-26^{m-1}*h[i-1])+ h[i+m-1]\]

其中 \(26^{m-1}\) 這個指數項可以事先計算出來放在一個數組中,當我們需要的時候,就從對應下標中取出來即可。

可以看到,計算雜湊值的時候,我們只需要遍歷一次主串即可計算出所有子串的雜湊值,這部分時間複雜度為 O(n)。模式串和子串需要比較 n-m+1 次雜湊值,這部分時間複雜度也為 O(n)。所以,RK 演算法總的時間複雜度為 O(n)。

但是,如果模式串的長度很大,那麼計算出來的雜湊值就會超出計算機中整形資料可以表示的範圍。這時候,我們就可以犧牲一下,允許出現雜湊衝突,比如可以求所有字元的對映和等,這種情況下雜湊值的範圍就小很多了。此時,當兩個子串雜湊值相同時,我們需要再進一步確定二者本身是否是相同的。

參考資料-極客時間專欄《資料結構與演算法之美》

獲取更多精彩,請關注「seniusen」!