本文是學習極客時間王爭《數據結構與算法之美》的個人總結,圖片均來源於其中。
參考地址:https://time.geekbang.org/column/article/71187
BF算法和RK算法都是但模式匹配算法,也就是一個串跟一個串進行匹配。
BF算法
BF(Brute Force)算法,即暴力匹配算法,也叫朴素匹配算法。
如果在字符串A中查找字符串B,那么字符串A就是主串,字符串B就是模式串。把主串得長度記為n,模式串得長度記為m,n>=m。
BF算法得思想是:我們在主串中,檢查起始位置分別為0,1,2... n-m,且長度為m得n-m+1個子串,看有沒有跟模式串匹配的。
看下面的圖片示意:

從上面的算法思想和例子可以看出,在極端情況下,我們每次要對比m個字符,要對比n-m+1次,所以這種算法的最壞時間復雜度為O(n*m)。
雖然理論上BF算法的時間復雜度很高,但是在實際開發中是一個比較常用的字符串匹配算法,原因如下:
- 第一,實際的軟件開發中,大多情況模式串和主串的長度不會太長。而且每次模式串於主串的子串匹配的時候,中途遇到不匹配的字符時就停止,並不需要把m個字符都比較一下。所以,大部分情況下算法的執行效率比較高。
- 第二,BF算法思想簡單,實現容易,不容易出錯,如果有bug也容易暴露和修復,所以在滿足性能要求的前提下,簡單是首選,即KISS(Keep it Simple and Stupid)設計原則。
RK算法
RK算法全稱為Rabin-Karp算法,即由Rabin和Karp兩個人發明的。RK算法可以理解為BF算法的升級版。
在BF算法中,每次需要暴力的對比n-m+1個子串和模式串,每次最多需要對比m個字符,實際復雜度比較高。我們對其稍微進行改進,引入哈希算法,來降低時間復雜度。
RK算法思想:我們通過哈希算法對主串中的n-m+1個子串分別求哈希值,然后逐個與模式串的哈希值比較大小。如果某個子串的哈希值與模式串相等,那就說明對應的子串和模式串匹配了。
因為哈希值是一個數字,數字之間比較是否相等是非常快速的。不過在此之前我們還要遍歷子串中的每個字符,來計算子串的哈希值。模式串和子串的比較效率提高了,但是算法整體效率沒有提高。所以還要想辦法提高計算子串哈希值的效率。
我們假設要匹配的字符集中只包含K個字符,然后用一個K進制數來表示一個子串,這個K進制數轉化為十進制數,作為子串的哈希值。可以看下圖中的例子:
“657”看作十進制表示,“cba"看作26禁止表示,即只算小寫字母有26個字符。

下面以字符串中只包含a-z這26個小寫字符為例,在哈希值的計算中有一個特點,相鄰兩個子串s[i-1]和s[i] (i表示子串在主串中的起始位置,子串長度均為m),對應的哈希值計算公式有交集。由公式表示如下圖:

此外,在計算時為了提高效率,可以使用將26^(m-1)這部分計算先計算好存起來,后面使用時直接查表獲取。
RK算法的時間復雜度:
RK算法包含兩部分,計算子串哈希值和模式串哈希值與子串哈希值的比較。第一部分,可以設計特殊的哈希算法,只需要掃描一次主串便可以求出所有子串的哈希值。時間復雜度為O(n);第二部分哈希值直接的比較時間復雜度為O(1),總共需要比較n-m+1個子串,所以時間復雜度也為O(n)。所以整體的時間復雜度為O(n)。
其他可能遇到的問題:
如果模式串很長,相應的主串中的子串也會很長,通過上面的哈希算法計算的哈希值就會特別大,如果超過了計算機中的整形數據可以表示范圍,如何解決?
剛才設計的哈希算法是沒有散列沖突的,一個字符串與一個數值對應。但是為了能夠將哈希值落在整型數據范圍內,可以允許哈希沖突。我們可以更改哈希算法,a-z這26個字母每個對應一個數字,然后將字符串變成將每個字符對應的數字相加,最后的和作為哈希值,這個值就會相對小很多。或者將每一個字母從小到達對應一個素數,這樣的沖突概率會降低一些。
現在新的問題時,如何解決沖突,方法很簡單,當我們發現一個子串的哈希值跟模式串的哈希值相等時,在對比一下子串和模式串本身就好了。這里也要控制哈希算法沖突概率相對低一些,如果存在大量沖突,RK算法的時間復雜度也會退化,效率降低,極端情況下將會退化為O(n*m)。
思考:在RK算法指向時,可以邊計算主串中的子串,邊與模式串進行對比,如果哈希值相同,即匹配成功了,那么主串后面的子串就可以不用計算了,直接退出。
by:藍色的蛋卷兒
