關於字符串匹配算法有很多,之前我有講過一篇 KMP 匹配算法:圖解字符串匹配 KMP 算法,不懂 kmp 的建議看下,寫的還不錯,這個算法雖然很牛逼,但在實際中用的並不是特別多。至於選擇哪一種字符串匹配算法,在不同的場景有不同的選擇。
在我們平時文檔里的字符查找里
采用的就是 Boyer-Moore 匹配算法了,簡稱BM算法。這個算法也是有一定的難度,不過今天,我選用一個例子,帶大家讀懂這個字符串匹配 BM 算法,看完這篇文章,保證你能夠掌握這個算法的思想。
首先我先給出一個字符串和一個模式串
接下來我們要在字符串中查找有沒有和模式串匹配的字串,步驟如下:
壞字符
1、
和其他的匹配算法不同,BM 匹配算法,是從模式串的尾部開始匹配的,所以我們把字符串和模式串的尾部對齊。
顯然,從圖中我們可以發現,s 和 e 並不匹配。這時我們把“s” 稱之為壞字符,即代表不匹配的字符。而且我們可以發現,s 和模式串中的任意一個字符都不匹配,所以這時,我們可以直接把模式串移動到 s 的后面。
2、
從圖中可以看出,此時 p 和 e 不匹配,所以 p 是一個壞字符,不過,我們可以發現 “p” 包含在模式串中
所以,我們不能像第一步那樣,把模式串直接全部移到 p 的后面去,而是移動兩位,讓兩個 p 對齊。
4、
那么問題來了,當我們碰到碰到壞字符的時候,該移動幾位呢?
下面我和大家講一下這個問題,首先我們要算出模式串中兩個字符的下標。這兩個字符分別是
(1)模式串中與壞字符對應的那個字符的下標,在我們上面那個例子中,就是 e。
顯然,這個 e 的下標是 6(從0開始算起)。我們用變量 t1 來代表這個字符的下標吧。
(2)壞字符在模式串中的下標,在我們上面那個例子中,壞字符在模式串中的下標為 4,我們用變量 t2 來代表這個下標,如圖
找出這兩個字符的下標之后,我們就可以計算移動的位數了
移動的位數 = t1 - t2。
例如上面的例子步驟 2 中 t1 = 6, t2 = 4,所以移動了 t1 - t2 = 2 位。
(1)這個時候可能有人會問了,那如果模式串中有多個 p 呢?
答是如果有多個,我們只計算最右邊的那個(當然是移動的位數越少越安全了)
(2)可能又有人會問,那如果模式串中並不存在壞字符呢?例如步驟1
答是如果不存在的話,我們把 t2 賦值為 -1,即 t2 = -1。所以我們步驟 1 中移動了 t1 - t2 = 6 - (-1) = 7 位。
好了,現在我們已經解決了遇到壞字符之后,應該移動多少位的問題了。
好后綴
我們繼續匹配
5、
匹配,所以繼續匹配前面的字符
6、
匹配,繼續匹配前面的字符
7、
匹配,繼續匹配前面的字符
8、
匹配,繼續匹配前面的字符
9、
遇到壞字符 i,按照我們前面的規則,可以計算出 t1 = 2(就是a的下標)。t2 = -1(因為模式串不存在壞字符)。所以移動的位數是 t1 - t2 = 3。
但是,我想問一下,這是最好的移動方式嗎?有沒有更好的移動方法呢?接下來我就和大家介紹一種更好的方法,這種方法就是根據好后綴來移動位數。首先我們先介紹下啥的好后綴。
在上面的例子中,我們發現 "mple" 是能夠成功匹配的
我們把這些能夠成功匹配的子串,稱之為好后綴,所以呢,e,le,elp,mple 都是好后綴
因為 e, le, elp在之前的步驟中,也是能夠成功匹配。不過 mple 是最長的好后綴。
接下來我們要在模式串的前面尋找與好后綴匹配的子串,這句話的意思就是說,我們要在模式串中尋找這樣一個子串s:s 與好后綴匹配,並且s中的字符不能與好后綴有重疊。
我舉個例子吧,例如有模式串 abcddab,然后好后綴分別是 b, ab, dab。那么與好后綴匹配的字串有 b,ab。(因為abcddab前面中的b可以與好后綴 b 匹配,前面的 ab 與好后綴 ab 匹配)。不過,沒有與好后綴 dab 匹配的子串。
那么問題來了,如果我們找到了多個這樣的子串的話,我們要選擇哪一個呢?例如上面我們找到了兩個,分別是 a,ab。
這個時候,我們選擇與比較長的那個好后綴匹配的子串,例如,上面的例子中,我們會選擇 ab,我們把這個被選中的子串(ab)稱之為好前綴吧(我是為了后面方便描述,才給它這個一個稱呼)。
找出了好后綴和好前綴之后 ,我們就可以知道要移動幾位了,公式如下:
移動的位數 = 好后綴的下標 - 好前綴的下標。
當然,好后綴有多個,我們是選擇和好前綴匹配的那一個。那么好后綴的下標怎么算呢?,計算方法是按照好后綴的最后一個字符的下標為准,例如模式串 abcddab 中好后綴 ab 的下標為 6(下標從 0 開始算起)。好前綴下標的方法也是一樣,以最后一個字符的下標位准,例如模式串 abcddab 中,好前綴 ab 的下標為 1。
這里可能有人會問,那如果不存在這樣的好前綴呢?如果不存在的話,就用 -1 充當好前綴的下標。
知道了移動位數之后,我們繼續來匹配我們上面的例子
10、
好后綴是 e, le, ple, mple,但是模式串中只有一個子串能夠與好后綴 e 匹配,所以好前綴為 e。
顯然,這個時候好前綴 e 的下標為 0,好后綴 e 的下標為 6,所以移動的位數為 6。如果按照我們最開始壞字符的移動規則的話,只能移動 3 位,而用好后綴可以移動 6 位。
選擇壞字符的規則還是好后綴?
11、
可能有人會問,兩個規則我們應該要選擇哪一個呢?
答案很簡單,把兩個規則的移動位數都算出來,選擇移動位數多的就是了。
這里 p 是壞字符,並且不存在好后綴,所以采用壞字符的規則,移動 2 位。
12、
這個時候,我們可以發現,模式串的字符全部都匹配了,這也意味着匹配結束了。
總結
這篇文章我是采用直接舉例子的方式來講,我覺得這樣反而容易懂,並且在講的過程中,可能沒有講的那么全,這是因為我不想說的太全,因為把所有情況都羅列處理的話,相信你容易暈。所以我才用這種方式,讓你先懂了這個 BM 的算法思想,之后的細節,你可以再去琢磨。
為了講清楚這個算法,也算是絞盡腦汁,特別是為了能夠以最簡單的方式來講解好后綴的規則,停筆思索了好久,最后也百度搜索了幾篇文章,看看別人都怎么講,還翻開了我之前購買的數據結構與算法的專欄,,,最后結合自己的想法寫了出來。希望這篇文章,能夠讓你讀懂給 BM 算法,這個算法的核心就是壞字符和好后綴了,相信你一定能夠搞懂!后面會連續講解幾篇與樹有關的文章。
如果你覺得這篇內容對你挺有啟發,為了讓更多的人看到這篇文章:不妨
1、點贊,讓更多的人也能看到這篇內容(收藏不點贊,都是耍流氓 -_-)
2、關注我和專欄,讓我們成為長期關系
3、關注公眾號「苦逼的碼農」,主要寫算法、計算機基礎之類的文章,里面已有100多篇原創文章
大部分的數據結構與算法文章被各種公眾號轉載相信一定能讓你有所收獲
我也分享了很多視頻、書籍的資源,以及開發工具,歡迎各位的關注我的公眾號:苦逼的碼農,第一時間閱讀我的文章。