字符串匹配有多種方法,這裏先講最簡單的兩種算法: BF算法 和 RK算法,複雜度也相對較高。
它們均爲單模式串匹配的算法,也就是一個串跟一個串進行匹配。
BF算法
簡介
Brute Force,暴力匹配算法,也叫樸素匹配算法。
比較簡單、好懂,但相應的性能也不高。
在字符串 A 中查找字符串 B ,那字符串 A 就是主串,字符串 B 就是模式串。
主串的長度記作 n ,模式串的長度記作 m ,所以n>m 。
算法思想
在主串中,檢查起始位置分別是 0 、1、 2…n-m 且長度爲 m 的 n-m+1 個子串,看有沒有跟模式串匹配的。
時間複雜度
極端情況下,要比較 n-m+1 次,每次需要比對 m 個字符。所以最壞情況時間複雜度O(n*m)。
常用原因
雖然複雜度高,但實際開發中很常用,原因如下:
- 大部分情況下,模式串和主串的長度都不會太長。每次模式串與主串中的子串匹配時,當中途遇到不能匹配的字符的時候,就可以就停止了,不需要把 m 個字符都比對;
- 算法思想簡單,代碼實現簡單。簡單意味着不容易出錯,如果有 bug 也容易暴露和修復。
RK算法
Rabin-Karp,由兩位發明者名字而來。可以看做是BF的升級版。
算法思想
通過哈希算法對主串中的 n-m+1 個子串分別求哈希值,然後逐個與模式串的哈希值比較大小。
如果某子串哈希值與模式串相等,那就說明對應子串和模式串匹配(先不考慮衝突)。
哈希值是一個數字,數字之間比較是否相等是非常快速。
提高哈希計算效率
哈希算法計算子串的哈希值時,仍需要遍歷子串中的每個字符,需要改進。
改進方法: 要匹配的字符串的字符集中只包含 K 個字符,可以用一個 K 進制數來表示一個子串,這個 K 進制數轉化成十進制數,作爲子串的哈希值。
舉例解釋上述方法。
假如處理的字符串只包含 a-z 這26 個小寫字母,那用二十六進制來表示一個字符串。
我們把26 個字符映射到 0~25 這 26 個數字。
再把二十六進制數轉化成十進制數,進位從 10 改成 26 就可以。
這種計算方法,在主串中相鄰兩個子串的哈希值的計算公式有一定關係。
規律:相鄰兩個子串 s[i-1] 和 s[i] ( i 表示子串在主串中的起始位置,子串的長度都爲 m ),對應的哈希值計算公式有交集。
公式表示如下:
事先計算好 、 、 …… ,存儲在長度爲 的數組中,公式中的 “ 次方 ” 就對應數組的下標。
時間複雜度
第一部分:通過設計特殊的哈希算法,只需掃描一遍主串就能計算出所有子串的哈希值,所以時間複雜度是 O(n) 。
第二部分:需要比較 n-m+1 個子串的哈希值,比較的複雜度是O(1),總的複雜度也是 O(n)。
所以,RK 算法整體的時間複雜度就是O(n) 。
解決衝突問題
上面的哈希算法沒有衝突,但是指數計算可能導致哈希值結果非常大,超出計算機表示範圍。
解決方法就是允許衝突,從而減小計算範圍。
允許衝突的哈希算法很多,舉一個例子。
將每一個字母從小到大對應一個素數,把字符串中每個字母對應的數字相加,得到的和作爲哈希值。
怎麼解決衝突呢?其實很簡單。
比較完哈希值後,
不相等的話,則無須比較,肯定不匹配;
相等的話,把兩個字符串本身再比較一次即可。
這種方法前提是要控制衝突概率,達到可以接受的狀態,否則算法會退化的厲害,變成 O(n*m) 。
本文是極客時間王爭 數據結構與算法 課程的筆記,推薦此課,喜歡可以購買課程。