[數據結構拾遺]子字符串匹配經常使用算法總結

在這裏插入圖片描述

前言

新開專欄【數據結構拾遺】html

本專欄旨在快速瞭解常見的數據結構和算法。在須要使用到相應算法時,可以幫助你回憶出經常使用的實現方案而且知曉其優缺點和適用環境。git

參考

子字符串匹配

子字符串匹配算法的定義:github

  • 文本長度:N
  • 模式字符串長度:M
  • 有效位移:s

在這裏插入圖片描述

解決字符串匹配的算法有很是多,目前經常使用的有如下幾種:算法

  • 暴力查找
  • KMP 算法
  • Boyer-Moore算法
  • Rabin-Karp指紋字符串查找

字符串匹配算法一般分爲兩個步驟:預處理(Preprocessing)和匹配(Matching)。因此算法的總運行時間爲預處理和匹配的時間的總和。後端

經常使用算法

暴力查找

參考:安全

www.cnblogs.com/gaochundong…bash

樸素的字符串匹配算法又稱爲暴力匹配算法(Brute Force Algorithm),它的主要特色是:微信

  • 沒有預處理階段;
  • 滑動窗口老是後移 1 位;
  • 對模式中的字符的比較順序不限定,能夠從前到後,也能夠從後到前;
  • 匹配階段須要 O((n - m + 1)m) 的時間複雜度;
  • 須要 2n 次的字符比較;

KMP 算法

參考:數據結構

www.ruanyifeng.com/blog/2013/0…數據結構和算法

詳細過程:

在這裏插入圖片描述

從左到右匹配,直到匹配到第一個字符相等,以下圖所示,而後繼續匹配後面的字符。

在這裏插入圖片描述

到了D,發現不對,這是若是暴力法,則直接將模式後移一位,從新匹配。KMP算法的想法是,設法利用這個已知信息,不要把"搜索位置"移回已經比較過的位置,繼續把它向後移,這樣就提升了效率。

在這裏插入圖片描述

在查找的一開始根據模式字符串,生成一張《部分匹配表》(Partial Match Table)

在這裏插入圖片描述

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

因此移動爲數 = 6 - 2 =4

在這裏插入圖片描述

這個《部分匹配表》如何生成?

"部分匹配值"就是"前綴"和"後綴"的最長的共有元素的長度。以"ABCDABD"爲例,

- "A"的前綴和後綴都爲空集,共有元素的長度爲0;

- "AB"的前綴爲[A],後綴爲[B],共有元素的長度爲0;

- "ABC"的前綴爲[A, AB],後綴爲[BC, C],共有元素的長度0;

- "ABCD"的前綴爲[A, AB, ABC],後綴爲[BCD, CD, D],共有元素的長度爲0;

- "ABCDA"的前綴爲[A, AB, ABC, ABCD],後綴爲[BCDA, CDA, DA, A],共有元素爲"A",長度爲1;

- "ABCDAB"的前綴爲[A, AB, ABC, ABCD, ABCDA],後綴爲[BCDAB, CDAB, DAB, AB, B],共有元素爲"AB",長度爲2;

- "ABCDABD"的前綴爲[A, AB, ABC, ABCD, ABCDA, ABCDAB],後綴爲[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的長度爲0。
複製代碼

Python和Java實現參考本身的博客:

blog.csdn.net/qqxx6661/ar…

Boyer-Moore

參考:

www.ruanyifeng.com/blog/2013/0…

幾種常見的字符串匹配算法的性能比較:

在這裏插入圖片描述

KMP算法並非效率最高的算法,實際採用並很少。各類文本編輯器的"查找"功能(Ctrl+F),大多采用Boyer-Moore算法。

詳細過程:

在這裏插入圖片描述

首先,"字符串"與"搜索詞"頭部對齊,從尾部開始比較。咱們看到,"S"與"E"不匹配。這時,"S"就被稱爲"壞字符"(bad character),即不匹配的字符。咱們還發現,"S"不包含在搜索詞"EXAMPLE"之中,這意味着能夠把搜索詞直接移到"S"的後一位。

在這裏插入圖片描述

依然從尾部開始比較,發現"P"與"E"不匹配,因此"P"是"壞字符"。可是,"P"包含在搜索詞"EXAMPLE"之中。因此,將搜索詞後移兩位,兩個"P"對齊。

"壞字符規則":後移位數 = 壞字符的位置 - 搜索詞中的上一次出現位置(若是"壞字符"不包含在搜索詞之中,則上一次出現位置爲 -1)

上圖中,比較的是P和E,出如今第6位(0開始),而後P上一次位置是4,因此6-4=2

接着繼續,一直比較到M:

在這裏插入圖片描述

在這裏插入圖片描述

在這裏插入圖片描述

根據"壞字符規則",此時搜索詞應該後移 2 - (-1)= 3 位。問題是,此時有沒有更好的移法?

比較前面一位,"MPLE"與"MPLE"匹配。咱們把這種狀況稱爲"好後綴"(good suffix),即全部尾部匹配的字符串。注意,"MPLE"、"PLE"、"LE"、"E"都是好後綴

"好後綴規則":後移位數 = 好後綴的位置 - 搜索詞中的上一次出現位置

這個規則有三個注意點:

(1)"好後綴"的位置以最後一個字符爲準。假定"ABCDEF"的"EF"是好後綴,則它的位置以"F"爲準,即5(從0開始計算)。

(2)若是"好後綴"在搜索詞中只出現一次,則它的上一次出現位置爲 -1。好比,"EF"在"ABCDEF"之中只出現一次,則它的上一次出現位置爲-1(即未出現)。

(3)若是"好後綴"有多個,則除了最長的那個"好後綴",其餘"好後綴"的上一次出現位置必須在頭部。好比,假定"BABCDAB"的"好後綴"是"DAB"、"AB"、"B",請問這時"好後綴"的上一次出現位置是什麼?回答是,此時採用的好後綴是"B",它的上一次出現位置是頭部,即第0位。這個規則也能夠這樣表達:若是最長的那個"好後綴"只出現一次,則能夠把搜索詞改寫成以下形式進行位置計算"(DA)BABCDAB",即虛擬加入最前面的"DA"。

回到上文的這個例子。此時,全部的"好後綴"(MPLE、PLE、LE、E)之中,只有"E"在"EXAMPLE"還出如今頭部,因此後移 6 - 0 = 6位。

能夠看到,"壞字符規則"只能移3位,"好後綴規則"能夠移6位。因此,Boyer-Moore算法的基本思想是,每次後移這兩個規則之中的較大值。

Boyer–Moore 算法的精妙之處在於,其經過兩種啓示規則來計算後移位數,且其計算過程只與模式 P 有關,而與文本 T 無關。所以,在對模式 P 進行預處理時,可預先生成 "壞字符規則之向後位移表" 和 "好後綴規則之向後位移表",在具體匹配時僅需查表比較二者中最大的位移便可。

Rabin-Karp

參考:

www.cnblogs.com/tanxing/p/6…

首先計算模式字符串的散列函數, 若是找到一個和模式字符串散列值相同的子字符串, 那麼繼續驗證二者是否匹配.

這個過程等價於將模式保存在一個散列表中, 而後在文本中的全部子字符串查找. 但不須要爲散列表預留任何空間, 由於它只有一個元素.

基本思想

長度爲M的字符串對應着一個R進制的M位數, 爲了用一張大小爲Q的散列表來保存這種類型的鍵, 須要一個可以將R進制的M位數轉化爲一個0到Q-1之間的int值散列函數, 這裏能夠用除留取餘法.

舉個例子, 須要在文本 3 1 4 1 5 9 2 6 5 3 5 8 9 7 9 3 查找模式 2 6 5 3 5, 這裏R=10, 取Q=997, 則散列值爲

2 6 5 3 6 % 997 = 613
複製代碼

而後計算文本中全部長度爲5的子字符串並尋找匹配

3 1 4 1 5 % 997 = 508

1 4 1 5 9 % 997 = 201

......

2 6 5 3 6 % 997 = 613 (匹配)
複製代碼

計算散列函數

在實際中,對於5位的數值, 只須要使用int就能夠完成全部須要的計算, 可是當模式長度太大時, 咱們使用Horner方法計算模式字符串的散列值

2 % 997 = 2

2 6 % 997 = (2*10 + 6) % 997 = 26

2 6 5 % 997 = (26*10 + 5) % 997 = 265

2 6 5 3 % 997 = (265*10 + 3) % 997 = 659

2 6 5 3 5 % 997 = (659*10 + 5) % 997 = 613

這裏關鍵的一點就是在於不須要保存這些數的值, 只需保存它們除以Q以後的餘數.

取餘操做的一個基本性質是若是每次算術操做以後都將結果除以Q並取餘, 這等價於在完成全部算術操做以後再將最後的結果除以Q並取餘.

算法實現:

構造函數爲模式字符串計算了散列值patHash並在變量中保存了R^(M-1) mod Q的值, hashSearch()計算了文本前M個字母的散列值並和模式字符串的散列值比較, 若是沒有匹配, 文本指針繼續下移一位, 計算新的散列值再次比較,知道成功或結束.

Java代碼:

www.cnblogs.com/tanxing/p/6…

蒙特卡洛算法和拉斯維加斯算法區別:

在這裏插入圖片描述

總結

優勢:

  • 暴力查找算法:實現簡單且在通常狀況下工做良好(Java的String類型的indexOf()方法就是採用暴力子字符串查找算法);
  • Knuth-Morris-Pratt算法可以保證線性級別的性能且不須要在正文中回退;
  • Boyer-Moore算法的性能通常狀況下都是亞線性級別;
  • Rabin-Karp算法是線性級別;

缺點:

  • 暴力查找算法所需時間可能和NM成正比;
  • Knuth-Morris-Pratt算法和Boyer-Moore算法須要額外的內存空間;
  • Rabin-Karp算法內循環很長(若干次算術運算,其餘算法都只須要比較字符);

在這裏插入圖片描述

在這裏插入圖片描述

關注我

我是蠻三刀把刀,後端開發。主要關注後端開發,數據安全,爬蟲等方向。微信:yangzd1102

Github:@qqxx6661

我的博客:

原創博客主要內容

  • Java知識點複習全手冊
  • Leetcode算法題解析
  • 劍指offer算法題解析
  • SpringCloud菜鳥入門實戰系列
  • SpringBoot菜鳥入門實戰系列
  • Python爬蟲相關技術文章
  • 後端開發相關技術文章

我的公衆號:Rude3Knife

我的公衆號:Rude3Knife

若是文章對你有幫助,不妨收藏起來並轉發給您的朋友們~

相關文章
相關標籤/搜索