演算法之 字串匹配演算法

2021-09-24 08:17:29 字數 4100 閱讀 3589

一說到兩個字串匹配,我們很自然就會想到用兩層迴圈來匹配,用這種方式就可以實現乙個字串是否包含另乙個字串了,這種演算法我們稱為 bf演算法。

bf演算法,即暴力(brute force)演算法,是普通的模式匹配演算法,bf演算法的思想就是將目標串 s 的第乙個字元與模式串 t 的第乙個字元進行匹配,若相等,則繼續比較 s 的第二個字元和 t的第二個字元;若不相等,則比較 s 的第二個字元和 t 的第乙個字元,依次比較下去,直到得出最後的匹配結果。

bf演算法實現

public

static

intbruteforce

(string s,string t)

if(j == plength)

}return -1;

}複製**

bf演算法是一種蠻力演算法,沒有任何優化,就是用兩層迴圈的比較,當字串比較大的時候,執行效率就非常低下,不適合比較非常大的字串。

該演算法最壞情況下要進行

雖然暴力搜尋很容易實現,並且如果解決方案存在它就一定能夠找到,但是它的代價是和候選方案的數量成比例的,由於這一點,在很多實際問題中,消耗的代價會隨著問題規模的增加而快速地增長。因此,當問題規模有限,或當存在可用於將候選解決方案的集合減少到可管理大小的針對特定問題的啟發式演算法時,通常使用暴力搜尋。另外,當實現方法的簡單度比運算效率更重要的時候,也會用到這種方法。

我們看到暴力搜尋演算法雖然不需要預處理字串,但效率比較低下,因為它需要做很多不必要的匹配,因此我們需要更高效的演算法。

aho–corasick演算法是由 alfred v. aho 和 margaret j.corasick 發明的字串搜尋演算法,用於在輸入的一串字串中匹配預先構建好的 trie 樹中的子串。它與普通字串匹配的不同點在於同時與所有字典串進行匹配。演算法均攤情況下具有近似於線性的時間複雜度,約為字串的長度加所有匹配的數量。

假設 m 為模式的長度, n 為要搜尋的字串長度, k為字母表長度。該演算法由於需要預先構建好變異的 trie 樹,因此需要

該演算法主要依靠構造乙個有限狀態機(類似於在乙個 trie 樹中新增失配指標)來實現。這些額外的失配指標允許在查詢字串失敗時進行回退(例如設 trie 樹的單詞 cat 匹配失敗,但是在 trie 樹中存在另乙個單詞 cart,失配指標就會指向字首 ca ),轉向某字首的其他分支,免於重複匹配字首,提高演算法效率。

當乙個字典串集合是已知的(例如乙個計算機病毒庫), 就可以以離線方式先將自動機求出並儲存以供日後使用,在這種情況下,演算法的時間複雜度為輸入字串長度和匹配數量之和,也是 ac自動機演算法常用的情況。

構造

1.一棵根據單詞或者內容構建 trie 樹。這個也叫 goto表。

2.在乙個單詞結束的地方,會有指向乙個 trie樹根或者其他分支上和它相同的字元,這個就是回退指標,免於重複匹配字首,提高演算法效率。比如下圖中的單詞 his 結尾的 s 指向 she 的 s,就可以在原有的基礎上繼續向下查詢,減少字首的匹配次數。這個也叫 fail表。

查詢

先查詢當前節點的「孩子節點」,如果沒有找到匹配,查詢它的字尾節點的孩子,如果仍然沒有,接著查詢字尾節點的對應 fail表的字尾節點的孩子, 如此迴圈, 直到根節點,如果到達根節點仍沒有找到匹配則結束。

當演算法查詢到乙個節點,則輸出所有結束在當前位置的字典項。輸出步驟為首先找到該節點的字典字尾,然後用遞迴的方式一直執行到節點沒有字典字首為止。同時,如果該節點為乙個字典節點,則輸出該節點本身。

實現

在 github 上有個開源的實現,有興趣的同學可以細讀。github.com/robert-bor/…

有沒有不需要提前做那麼多處理,後期匹配又快的演算法呢?

答案就是 kmp演算法。

它以三個發明者命名,起頭的那個 k 就是著名科學家 donald knuth。

kmp演算法示例 這個演算法的關鍵在於 部分匹配表(partial match table,也叫 pmt表),那這個部分匹配表怎麼來的呢?我們看下模式串 「abababca」 的部分匹配表:

char:  | a | b | a | b | a | b | c | a |

index: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |

value: | 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 |

複製**

對應的 value,就是模式串 「abababca」 的部分匹配表。

要理解部分匹配表,我們需要先知道字首和字尾。

"字首"指除了最後乙個字元以外,乙個字串的全部頭部組合;

"字尾"指除了第乙個字元以外,乙個字串的全部尾部組合。

字串:knuth

字首: k, kn, knu, knut

字尾: nuth, uth, th, h

複製**

現在我們根據"字首"和"字尾"來求出模式串 「abababca」 的部分匹配表。

1. "a" 的字首和字尾都為空集,沒有共有元素,長度為 0;

2. "ab" 的字首為[a],字尾為[b],沒有共有元素,長度為 0;

3. "aba" 的字首為[a, ab],字尾為[ba, a],共有元素 "a", 長度為 1;

4. "abab" 的字首為[a, ab, aba],字尾為[bab, ab, b],共有元素 "ab", 長度為 2;

...7. "abababc" 的字首為[a, ab, aba, abab, ababa, ababab],字尾為[bababc, ababc, babc, bc, c],沒有共有元素, 長度為 0;

8. "abababca" 的字首為[a, ab, aba, abab, ababa, ababab, abababc],字尾為[bababca, ababca, babca, bca, ca, a],共有元素 "a", 長度為 1;

複製**

怎麼根據部分匹配表查詢呢?當我們找到部分匹配時,我們可以使用部分匹配表中的值來跳過(而不是重做不必要的舊比較)。

移動位數 = 已匹配的字元數 - 對應部分匹配錶值

複製**

我們將 「abababca」 模式串與 「bacbababaabcbab」 字串的匹配舉例。

bacbababaabcbab

| abababca

複製**

第一步,由於我們的模式串開頭是 "a", 然後迴圈字串,第二個字元就是 "a",這說明有乙個字元匹配。由於下乙個字元不匹配,因此我們需要移動。移動的位數等於 部分匹配表[ 已匹配的字元數 - 1 ],也就是部分匹配表中下表為 0 的值,為 0。因此我們需要向後跳過 0 個字元,繼續迴圈。

bacbababaabcbab

|||||

abababca

複製**

現在匹配了 5 個字元,需要跳過的字元數就是 5 - 3 = 2,需要向後移動 2 位。

// x 表示跳過的字元

bacbababaabcbab

xx|||

abababca

複製**

現在匹配了 3 個字元,需要跳過的字元數就是 3 - 1 = 2,需要向後移動 2 位。

bacbababaabcbab

xx|abababca

複製**

此時,我們的模式比文字中的其餘字元長,所以我們知道並沒有匹配。

bf演算法 屬於暴力破解的方式,利用窮舉來實現字串的查詢,當字串太長的時候,查詢速度是非常慢的。

於是又有了 ac自動機演算法,它是先構建乙個類似 trie樹 的結構,在根據 trie樹 查詢對應的字串查,它的時間複雜度差不多是線性的,但需要提前構建好 trie樹。

有沒有一種演算法既不是窮舉的方式,也不需要提前構建好 trie樹。

那就是大名鼎鼎的 kmp演算法,它通過運用對這個詞在不匹配時本身就包含足夠的資訊來確定下乙個匹配將在**開始的,從而避免重新檢查先前匹配的字元,也就是我們上面的部分匹配表(pmt表)。

字串匹配演算法 字串匹配演算法總覽

字串匹配在文字處理裡非常重要,我們採用簡潔的python 把以下演算法一一實現並講解。樸素演算法 algorithm rabin karp 演算法 有限自動機演算法 finite automation knuth morris pratt 演算法 kmp algorithm boyer moore ...

字串匹配演算法之SimHash演算法

由於實驗室和網際網路基本沒啥關係,也就從來沒有關注過資料探勘相關的東西。在實際工作中,第一次接觸到匹配和聚類等工作,雖然用一些簡單的匹配演算法可以做小資料的聚類,但資料量達到一定的時候就束手無策了。所以,趁著週末把這方面的東西看了看,做個筆記。google的 detecting near dupli...

字串匹配演算法之kmp演算法

kmp演算法是一種效率非常高的字串匹配演算法,是由knuth,morris,pratt共同提出的模式匹配演算法,所以簡稱kmp演算法 在乙個字串中查詢另乙個字串時,會遇到如下圖的情況 我們通常的做法是從第乙個串a的下一位b再逐位比較,但這樣的做法非常低效。仔細思考一下發現,第乙個串已經匹配的部分就是...