【算法】字符串匹配算法


  前幾天打算一直想找一個時間把字符串匹配算認真弄一下,今天不想看其他的東西,那就想着把字符串匹配算法好好整理梳理一下。

  字符串匹配算法有幾種相對比較出名的,分別是BF(暴力破解),RK()、BM()、KMP()。下文中 主串為被匹配的串, 模式串為匹配的串。 例如 s = “aabbcc”(主串),a = “aa" (模式串)

BF算法


   首先我大概把BF說一下,就是BF(Brute Force)顧名思義就是暴力破解算法,意思就是我們在主串中,檢查的起始位置分別是 0、1、2…n-m 且長度為 m 的 n-m+1 個子串(n為主串的長度, m為模式串的長度),看有沒有跟模式串匹配的。這種算法的時間復雜度為O(n*m), 空間復雜度為O(1)。

  雖然BF算法的時間復雜度高,但是如果在主串和模式串的長度不長時,這種算法的效率也不低,而且時間復雜度O(n*m)是最壞的時間復雜度,大部分情況下都不會出現這種情況。另外這種方法也是最簡單和易於理解的。

   圖示步驟

RK算法


   主串的長度為n, 模式串的長度為m,在BF算法中相當於我們將主串分成n-m+1個字串,然后使用模式串依次對這n-m-1個字串進行匹配。而在RK算法的思想就是我們根據模式串的長度求出主串中n-m+1個字串,然后分別對這n-m+1個字串進行hash求值,最后使用模式串的hash值進行比對,如果相等,為了防止hash沖突我們在對兩個hash值對應的串進行對比是否相等。從而得出結果。這種算法時間復雜度為O(n),空間復雜度為O(1)。

  圖示

 BM算法


  前面兩種匹配算法中當遇到不匹配的字符時,BF 算法和 RK 算法的做法都是將模式串往后移動一位,然后從模式串的第一個字符開始重新匹配。一直到最后完成匹配為止。這兩種都是從頭部開始匹配,但是對於BM算法中他主要考慮和改進了前兩種算法的不足,他主要是從模式串的尾部到頭部依次對主串進行匹配的。而在匹配的過程中每一次向后移動幾位是由兩個規則決定出來的。即壞字符規則好后綴規則。

 壞字符規則

  我們從模式串的末尾往前倒着匹配,當我們發現某個字符沒法匹配的時候。我們把這個沒有匹配的字符叫作壞字符。

  這里的情況分成兩種:

  (1)如果發現模式串中並不存在這個字符,我們可以將模式串直接往后滑動到壞字符的后面一位,再從模式串的末尾字符開始比較。

  (2)如果發現模式串中存在這個字符,這里我們假定這個壞字符對應於模式串中的下標是x, 而壞字符在模式串出現離下標x前最近的位置是y(防止壞字符在模式串中多次出現,導致滑動過頭的情況),這時應該移動x-y位,然后重新開始從尾進行匹配。

    好后綴規則

  好后綴規則主要是為了防止x-y的值為負數,例如主串是' aaaaaaaaaaaaaaaa',模式串是' baaa'。這里不但不會向后滑動模式串,還有可能倒退。好后綴意思就是模式串后面已經匹配成功的字符串。

  我們把已經匹配的z字符串叫作好后綴,記作{u}。我們拿它在模式串中查找,這里也存在兩種情況

  (1)如果找到了另一個跟{u}相匹配的子串{u*},那我們就將模式串滑動到子串{u*}與主串中{u}對齊的位置。

  (2)如果好后綴在模式串中不存在可匹配的子串{u*},我們就查找{u}中的子串是否存在跟模式串的前綴子串匹配的結果。

   壞字符和好后綴進行比較來得出每次移動的位數

  我們可以分別計算好后綴和壞字符往后滑動的位數,然后取兩個數中最大的,作為模式串往后滑動的位數。這種處理方法還可以避免我們前面提到的,根據壞字符規則,計算得到的往后滑動的位數,有可能是負數的情況。

  代碼實現

  因此壞字符和好后綴兩者是獨立的,我們可以先實現壞字符部分,當遇到壞字符時,我們需要在得到兩個值一個就是主串中壞字符對應模式串的下標x, 然后得到主串中對應的壞字符在模式串中最靠近x的位置下標(如果每次都使用遍歷進行查找 這樣效率低,我們可以采用空間換時間的辦法,設置一個數組來記錄出現的位置,如果全是英文字母的話,數組大小為256,小標為asc碼,值為模式串的下標)

  例如

  初始化壞字符的輔助數組的代碼   

1 NUMS_SIZE = 256
2 
3 def generateBC(str_b, bc):
6     for i in range(len(str_b)):  # 將字符存儲到對應下標的位置
7         bc[ord(str_b[i])] = i

  第二個就是后綴子串的問題, 這里我們設置兩個輔助數組,一個是suffix數組,下標表示后綴字符的長度,值表示在前綴中出現的位置。另一個是prefix數組,下標表示后綴字符的長度,值表示是否是前綴字串。

  例如:

  初始化好后綴的輔助數組代碼(建議自己畫圖理解一下,不然代碼不好理解)

 1 def generateGS(str_b, suffix, prefix): # 針對模式串初始化相應的suffix數組和prefix數組。  2     str_b_length = len(str_b)   
 7     for i in range(str_b_length-1):  8         j, k = i, 0  9         while i >=0 and str_b[j] == str_b[str_b_length-1-k]: 10             j -= 1
11             k += 1
12             suffix[k] = j+1
13         if j < 0: 14             prefix[k] = True

  在寫完上面上個規則之后,我們來進行匹配的主體函數

 2 def moveByGS(j, m, suffix, prefix): # 找到好后綴對應的移動位數  3     k = m-1-j  4     if suffix[k] != -1:  5         return j - suffix[k]+1
 6     for i in range(j+2, m):  7         if prefix[m-i]:  8             return i  9     return m 10  
11         
12 def BM(str_a, str_b): # BM算法 13     str_a_len, str_b_len = len(str_a), len(str_b) 14     bc = [-1] * NUMS_SIZE 15  generateBC(str_b, bc) 16     suffix, prefix = [-1] * str_b_len, [False] * str_b_len 17  generateGS(str_b, suffix, prefix) 18     i = 0 19     while i <= (str_a - str_b_len): # 從第一個位置開始查找 20         j = str_b_len-1
21         while j >=0: 22             if str_a[i+j] != str_b[j]: 23                 break
24             j -= 1
25         if j < 0: 26             return i 27         x, y = j - bc[ord(str_a[i+j])], 0 # x表示壞字符移動的位數 28         if j < str_b-1: 29             y = moveByGS(j, str_b_len, suffix, prefix) # 得到好后綴的移動位數 30         i = i + max(x, y) # 取最大的進行移動 31     
32     return -1

KMP算法


 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM