字串匹配演算法(一)

公眾號程式設計師學長發表於2021-07-30

    字串匹配在工作中我們經常會用到,同時也是各大公司面試中的常考題目。字串匹配的演算法有很多,所以需要深入學習的東西也有很多。我們接下來會有一系列的文章去把字串匹配演算法儘量說明白。

    今天我們主要聊一下單模式串匹配演算法---即一個串去跟另外一個串去比較。在開始之前,為了後續方便講解,我們先明確兩個定義,即主串和模式串。如果我們要在長度為n的字串A中查詢長度為m字串B,那麼A就是主串,B就是模式串,其中n>m。我們先從最簡單的BF演算法說起。

BF演算法

      BF演算法也叫做暴力匹配演算法,也是最直接、簡單的演算法。所謂的暴力匹配演算法,就是固定主串,然後模式串一步步向前移動,一位一位的對比,直到在主串中找到相匹配的子串。如下圖所示。 

def bf(a, b):
    n = len(a)
    m = len(b)
​
    if n <= m:
        return 0 if b == a else -1
​
    for i in range(n-m+1):
        for j in range(m):
            if a[i+j] == b[j]:
                if j == m-1:
                    return i
                else:
                    continue
            else:
                break
    return -1
​
if __name__ == '__main__':
    a = 'cbdac'
    b = 'ac'
    start=bf(a, b)
    print('result:', start)
​
​
#####輸出####
result: 3

      從上面的程式碼我們可以看出,這種演算法的最壞情況時間複雜度是 O(n*m)。

RK演算法

       RK演算法的全稱叫Rabin-Karp演算法。是由它的兩位發明者 Rabin 和 Karp 的名字來命名的。RK演算法的思想就是通過比較2個字串的Hash值來判斷字串是不是相等的。我們在BF演算法中,如果主串的長度是n,模式串的長度是m,我們需要暴力的比較n-m+1個子串和模式串,來找出主串和模式串相匹配的子串。在子串和模式串比較的時候,需要一位一位的對比,所以BF演算法的時間複雜度較高,是O(N*M)。而RK演算法的思路是:通過雜湊演算法把n-m+1個子串分別求hash值,然後再和模式串的hash值比較大小。如果某個子串的雜湊值和模式串相等。那就說明對應的子串和模式串相匹配了(我們先忽略雜湊衝突的情況)。因為hash值的比較是非常快速的,所以子串和模式串比較的效率就提高了。如下圖所示。

     不過,通過雜湊演算法計算雜湊值的時候,是需要遍歷子串中的每個字元。雖然子串和模式串比較的效率提高了,但是演算法的整體效率卻沒有提高,那如何提高雜湊演算法計運算元串雜湊值的效率呢?這就需要設計一個更高效的雜湊演算法。我們假設要匹配的字串的字符集中只包含K個字元,我們可以用一個K進位制數來表示一個子串,這個K進位制數轉化成十進位制數,作為子串的雜湊值。我們舉個例子來說明一下。假如我們要處理的字串只含有a~z這26個小寫字母,我們把a~z對映到0~25這26個數字中,a表示0,b表示1,依次類推。所以字串"cdb"的雜湊值為:

 Hash("cdb")=c*26*26+d*26+b=2*26*26+3*26+1=1431

      這種雜湊演算法有一個特點,就是在主串中,相鄰兩個子串S[i-1]和S[i](其中i表示子串在主串中的起始位置),對應的雜湊值的計算公式是有交集的,也就是說我們可以根據S[i-1]的雜湊值,很快的計算出S[i]的雜湊值。我們來用公式表示一下。

 

      我們可以把26^0、26^1、26^2......26^(m-1)先計算出來,並且儲存在一個長度為m的陣列中,公式中的“次方”就對應陣列的下標。當我們需要計算26的x次方的時候,就可以從陣列的下標為x的位置取值,直接使用,這樣我們就省去了計算的時間。

      RK演算法中主要包括計運算元串的雜湊值和模式串雜湊值與子串雜湊值之間的比較。由於我們可以通過設計特殊的雜湊演算法,只需要掃描一遍主串就能計算出所有子串的雜湊值了,所以計運算元串雜湊值的時間複雜度為O(n)。

模式串雜湊值與每個子串雜湊值之間的比較的時間複雜度是O(1),總共需要比較 n-m+1 個子串的雜湊值,所以,這部分的時間複雜度也是 O(n)。所以,RK 演算法整體的時間複雜度就是 O(n)。

      還有一個問題需要注意,如果我們通過上面計算雜湊值的方法計算的雜湊值太大,超過了計算機表示的範圍,那我們該如何解決呢?剛剛我們設計的雜湊演算法是沒有雜湊衝突的,也就是說,一個字串與一個二十六進位制數一一對應,不同的字串的雜湊值肯定不一樣。實際上,我們為了能將雜湊值落在整型資料範圍內,可以犧牲一下,允許雜湊衝突。這個時候雜湊演算法該如何設計呢?其實很簡單,我們可以對一個大的素數取模。這樣的話就會帶來hash衝突的問題,如果有雜湊衝突的話,我們在發現一個子串的雜湊值跟模式串的雜湊值相等的時候,還需要去對比一下子串和模式串本身。

        好了,我們今天就先聊到這裡,下篇文章我們再來聊一下更高效的字串匹配演算法BM演算法和KMP演算法。

        更多硬核知識,請關注公眾號。

 

相關文章