1. 暴力算法 --bf算法
這是世界上最簡單的算法了。
首先將匹配串和模式串左對齊,然后從左向右一個一個進行比較,如果不成功則模式串向右移動一個單位。
假設匹配串文本長度為n,模式串長度為m,最差的情況下,時間復雜度為O(m*n).
bf算法每次匹配不成功的時候,前面匹配成功的信息都被當作廢物丟棄了,因此此方法速度最慢。
2. KMP算法
KMP算法的思想核心主要在Next數組。
在保證指針 i 不回溯的前提下,當匹配失敗時,讓模式串向右移動最大的距離;
並且可以在O(n+m)的時間數量級上完成對串的模式匹配操作;
在模式串和主串匹配時,各有一個指針指向當前進行匹配的字符(主串中是指針 i ,模式串中是指針 j ),在保證 i 指針不回溯的前提下,如果想實現功能,就只能讓 j 指針回溯。
j 指針回溯的距離,就相當於模式串向右移動的距離。 j 指針回溯的越多,說明模式串向右移動的距離越長。
計算模式串向右移動的距離,就可以轉化成:當某字符匹配失敗后, j 指針回溯的位置。
對於一個給定的模式串,其中每個字符都有可能會遇到匹配失敗,這時對應的 j 指針都需要回溯,具體回溯的位置其實還是由模式串本身來決定的,和主串沒有關系。
模式串中的每個字符所對應 j 指針回溯的位置,可以通過算法得出,得到的結果相應地存儲在一個數組中(默認數組名為 next )。
計算方法是:對於模式串中的某一字符來說,提取它前面的字符串,分別從字符串的兩端查看連續相同的字符串的個數,在其基礎上 +1 ,結果就是該字符對應的值。
例如:求模式串 “abcabac” 的 next 。前兩個字符對應的 0 和 1 是固定的。
對於字符 ‘c’ 來說,提取字符串 “ab” ,‘a’ 和 ‘b’ 不相等,相同的字符串的個數為 0 ,0 + 1 = 1 ,所以 ‘c’ 對應的 next 值為 1 ;
第四個字符 ‘a’ ,提取 “abc” ,從首先 ‘a’ 和 ‘c’ 就不相等,相同的個數為 0 ,0 + 1 = 1 ,所以,‘a’ 對應的 next 值為 1 ;
第五個字符 ‘b’ ,提取 “abca” ,第一個 ‘a’ 和最后一個 ‘a’ 相同,相同個數為 1 ,1 + 1 = 2 ,所以,‘b’ 對應的 next 值為 2 ;
第六個字符 ‘a’ ,提取 “abcab” ,前兩個字符 “ab” 和最后兩個 “ab” 相同,相同個數為 2 ,2 + 1 = 3 ,所以,‘a’ 對應的 next 值為 3 ;
最后一個字符 ‘c’ ,提取 “abcaba” ,第一個字符 ‘a’ 和最后一個 ‘a’ 相同,相同個數為 1 ,1 + 1 = 2 ,所以 ‘c’ 對應的 next 值為 2 ;
所以,字符串 “abcabac” 對應的 next 數組中的值為(0,1,1,1,2,3,2)。
上邊求值過程中,每次都需要判斷字符串頭部和尾部相同字符的個數,而在編寫算法實現時,對於某個字符來說,可以借用前一個字符的判斷結果,計算當前字符對應的 next 值。
具體的算法如下:
模式串T為(下標從1開始):“abcabac”
next數組(下標從1開始): 01
第三個字符 ‘c’ :由於前一個字符 ‘b’ 的 next 值為 1 ,取 T[1] = ‘a’ 和 ‘b’ 相比較,不相等,繼續;由於 next[1] = 0,結束。 ‘c’ 對應的 next 值為1;(只要循環到 next[1] = 0 ,該字符的 next 值都為 1 )
模式串T為: “abcabac”
next數組(下標從1開始):011
第四個字符 ’a‘ :由於前一個字符 ‘c’ 的 next 值為 1 ,取 T[1] = ‘a’ 和 ‘c’ 相比較,不相等,繼續;由於 next[1] = 0 ,結束。‘a’ 對應的 next 值為 1 ;
模式串T為: “abcabac”
next數組(下標從1開始):0111
第五個字符 ’b’ :由於前一個字符 ‘a’ 的 next 值為 1 ,取 T[1] = ‘a’ 和 ‘a’ 相比較,相等,結束。 ‘b’ 對應的 next 值為:1(前一個字符 ‘a’ 的 next 值) + 1 = 2 ;
模式串T為: “abcabac”
next數組(下標從1開始):01112
第六個字符 ‘a’ :由於前一個字符 ‘b’ 的 next 值為 2,取 T[2] = ‘b’ 和 ‘b’ 相比較,相等,所以結束。‘a’ 對應的 next 值為:2 (前一個字符 ‘b’ 的 next 值) + 1 = 3 ;
模式串T為: “abcabac”
next數組(下標從1開始):011123
第七個字符 ‘c’ :由於前一個字符 ‘a’ 的 next 值為 3 ,取 T[3] = ‘c’ 和 ‘a’ 相比較,不相等,繼續;由於 next[3] = 1 ,所以取 T[1] = ‘a’ 和 ‘a’ 比較,相等,結束。‘a’ 對應的 next 值為:1 ( next[3] 的值) + 1 = 2 ;
模式串T為: “abcabac”
next數組(下標從1開始):0111232
基於next的KMP算法的實現:
先看一下 KMP 算法運行流程(假設主串:ababcabcacbab,模式串:abcac)。
第一次匹配:
匹配失敗,i 指針不動,j = 1(字符‘c’的next值);
第二次匹配:
相等,繼續,直到:
匹配失敗,i 不動,j = 2 ( j 指向的字符 ‘c’ 的 next 值);
第三次匹配:
相等,i 和 j 后移,最終匹配成功。
3. horspool算法
Horspool是后綴搜索,也就是搜索已讀入文本中是否含有模式串的后綴;如果有,是多長,顯然,當后綴長度等於模式串的長度時,我們就找到了一個匹配。
Horspool算法思想:模式串從右向左進行匹配。對於每個文本搜索窗口,將窗口內的最后一個字符(C)與模式串的最后一個字符進行比較。如果相等,則繼續從后向前驗證其他字符,直到完全相等或者某個字符不匹配。然后,無論匹配與否,都將根據在模式串的下一個出現位置將窗口向右移動。模式串與文本串口匹配時,模式串的整體挪動,是從左往右,但是,每次挪動后,從模式串的最后一個字符從右往左進行匹配。
下面我們來看一個實例:
假設匹配串和模式串如下:
1.
匹配串:abcbcsdLinac-codecbcac
模式串:cbcac
首先從右向左進行匹配,c與c匹配成功,接着第二個字符b與a,匹配失敗(失配位置為3)。於是,從模式串當前位置往左尋找匹配失敗的那個字符,也即在模式串中尋找字符b上一次出現的位置(注意這里的“上一次”是指在模式串中從當前失配位置往左找到的第一個與失配位置相同的字符);結果我們在模式串中找到了字符b,其位置為1,那么就將模式串整體往右挪動,把剛才找到的字符b與之前與匹配串中失配的字符b對齊。總共移動了多少位呢?移動了(3-1)位。
2.
匹配串:abcbcsdLibac-codecbcac
模式串: cbcac
模式串整體挪動到b處對齊后,再從右向左開始匹配,此時發現其第一個需要匹配的字符d與c就匹配失敗(失配位置為4),尼瑪,坑爹啊!那接下來怎么辦?當然是跟上一步的方法一樣,在模式串中去找失配的那個字符d,如果在模式串中找到了d,將模式串平移,使其d字符與匹配串的d對齊。結果發現模式串中根本就沒有字符d。那接下來怎么辦?直接將模式串平移到剛才失配字符d后面的。這是因為模式串中沒有字符d,那么就不可能在匹配串中的d及其前面的字符中匹配成功。這一次我們移動的位數是4-(-1)=5位。
3.
匹配串:abcbcsdLibac-codecbcac
模式串: cbcac
然后,又回到第1步的那種狀態,從模式串的最后一個字符開始匹配,即c與c匹配,a與a匹配啊,然后發現b與c不匹配,從而我們在模式串中找b字符上一次出現的位置,發現其位置為1,移動模式串,將b字符與b字符對齊(如下圖),這次我們移動的位數是2-1=1位。
4.
匹配串:abcbcsdLibac-codecbcac
模式串: cbcac
發現模式串中不含-,則模式串移動到-后面那個字符。
5.
匹配串:abcbcsdLibac-codecbcac
模式串: cbcac
這一次,在e與a處出現不匹配,而且e也沒在模式串中出現過,那么模式串再右移到e的后面,這次移動的位數為:3-(-1)=4位。(為毛是減去-1?因為我們將模式串移動到失配字符的后面那個字符位置處去了,即相當將失配字符e與與模式串的第一個字符的前一個位置(-1處)對齊,懂否?)
6.
匹配串:abcbcsdLibac-codecbcac
模式串: cbcac
終於,在經歷第五步的那次挪動后,我們匹配成功了,是不是感覺匹配速度特別快?
有了以上實例,我們現在來抽取其一般規則,以方便編碼實現:
我們得到的規則只有一條,即:
字符串后移位數=失配字符位置-失配字符上一次出現的位置
如果失配字符根本就沒有出現在模式串中,我們將“失配字符上一次出現的位置”的值視為-1。
4. sunnday算法
Sunday算法是從前往后匹配,在匹配失敗時關注的是主串中參加匹配的最末位字符的下一位字符。
如果該字符沒有在模式串中出現則直接跳過,即移動位數 = 模式串長度 + 1;
否則,其移動位數 = 模式串長度 - 該字符最右出現的位置(以0開始) = 模式串中該字符最右出現的位置到尾部的距離 + 1。
下面舉個例子說明下Sunday算法。假定現在要在主串”substring searching”中查找模式串”search”。
剛開始時,把模式串與文主串左邊對齊:
結果發現在第2個字符處發現不匹配,不匹配時關注主串中參加匹配的最末位字符的下一位字符,即標粗的字符 i,因為模式串search中並不存在i,所以模式串直接跳過一大片,向右移動位數 = 匹配串長度 + 1 = 6 + 1 = 7,從 i 之后的那個字符(即字符n)開始下一步的匹配,如下圖:
結果第一個字符就不匹配,再看主串中參加匹配的最末位字符的下一位字符,是’r’,它出現在模式串中的倒數第3位,於是把模式串向右移動3位(m - 3 = 6 - 3 = r 到模式串末尾的距離 + 1 = 2 + 1 =3),使兩個’r’對齊,如下:
匹配成功。
回顧整個過程,我們只移動了兩次模式串就找到了匹配位置,緣於Sunday算法每一步的移動量都比較大,效率很高。
5.RK算法
算法的預處理時間為O(m),但它的在最壞情況下的時間復雜度為O((2n-m+1)m),而平均復雜度接近O(m+n)
把文本每m個字符構成的字符段作為一個字段,和模式進行匹配檢查。如果能對一個長度為m的字符串賦以一個Hash函數。那么顯然只有那些與模式具有相同hash函數值的文本中的字符串才有可能與模式匹配,沒有必要去考慮文本中所有長度為m的字段,因而大大提高了串匹配的速度。
將字符串的每一個字符看做一個數,那么這個字符串的就是一個數字數組,通過積分向量可以快速任意一個長度子字符串的向量和,可以把字符串的對應的字符數組的元素和看做這個字符串整體特征。
匹配串:aabsee sds
模式串 : ees
see向量和 == ees向量和,就對see和ees做逐個字符的比較,發現不匹配繼續往下走。
匹配串:aabeessds
模式串 : ees
ees向量和 == ees向量和 ,就對ees和ees做逐個字符的比較,發現匹配OK。
6.BM算法
1.
假定字符串為"HERE IS A SIMPLE EXAMPLE",搜索詞為"EXAMPLE"。
2.
首先,"字符串"與"搜索詞"頭部對齊,從尾部開始比較。
這是一個很聰明的想法,因為如果尾部字符不匹配,那么只要一次比較,就可以知道前7個字符(整體上)肯定不是要找的結果。
我們看到,"S"與"E"不匹配。這時,"S"就被稱為"壞字符"(bad character),即不匹配的字符。我們還發現,"S"不包含在搜索詞"EXAMPLE"之中,這意味着可以把搜索詞直接移到"S"的后一位。
3.
依然從尾部開始比較,發現"P"與"E"不匹配,所以"P"是"壞字符"。但是,"P"包含在搜索詞"EXAMPLE"之中。所以,將搜索詞后移兩位,兩個"P"對齊。
4.
我們由此總結出"壞字符規則":
后移位數 = 壞字符的位置 - 搜索詞中的上一次出現位置
如果"壞字符"不包含在搜索詞之中,則上一次出現位置為 -1。
以"P"為例,它作為"壞字符",出現在搜索詞的第6位(從0開始編號),在搜索詞中的上一次出現位置為4,所以后移 6 - 4 = 2位。再以前面第二步的"S"為例,它出現在第6位,上一次出現位置是 -1(即未出現),則整個搜索詞后移 6 - (-1) = 7位。
5.
依然從尾部開始比較,"E"與"E"匹配。
6.
比較前面一位,"LE"與"LE"匹配。
7.
比較前面一位,"PLE"與"PLE"匹配。
8.
比較前面一位,"MPLE"與"MPLE"匹配。我們把這種情況稱為"好后綴"(good suffix),即所有尾部匹配的字符串。注意,"MPLE"、"PLE"、"LE"、"E"都是好后綴。
9.
比較前一位,發現"I"與"A"不匹配。所以,"I"是"壞字符"。
10.
根據"壞字符規則",此時搜索詞應該后移 2 - (-1)= 3 位。問題是,此時有沒有更好的移法?
11.
我們知道,此時存在"好后綴"。所以,可以采用"好后綴規則":
后移位數 = 好后綴的位置 - 搜索詞中的上一次出現位置
舉例來說,如果字符串"ABCDAB"的后一個"AB"是"好后綴"。那么它的位置是5(從0開始計算,取最后的"B"的值),在"搜索詞中的上一次出現位置"是1(第一個"B"的位置),所以后移 5 - 1 = 4位,前一個"AB"移到后一個"AB"的位置。
再舉一個例子,如果字符串"ABCDEF"的"EF"是好后綴,則"EF"的位置是5 ,上一次出現的位置是 -1(即未出現),所以后移 5 - (-1) = 6位,即整個字符串移到"F"的后一位。
這個規則有三個注意點:
(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位。
12.
可以看到,"壞字符規則"只能移3位,"好后綴規則"可以移6位。所以,Boyer-Moore算法的基本思想是,每次后移這兩個規則之中的較大值。
更巧妙的是,這兩個規則的移動位數,只與搜索詞有關,與原字符串無關。因此,可以預先計算生成《壞字符規則表》和《好后綴規則表》。使用時,只要查表比較一下就可以了。
13.
繼續從尾部開始比較,"P"與"E"不匹配,因此"P"是"壞字符"。根據"壞字符規則",后移 6 - 4 = 2位。
14.
從尾部開始逐位比較,發現全部匹配,於是搜索結束。如果還要繼續查找(即找出全部匹配),則根據"好后綴規則",后移 6 - 0 = 6位,即頭部的"E"移到尾部的"E"的位置。
7.ac自動機
首先給定模式串"ash","shex","bcd","sha",然后我們根據模式串建立如下trie樹:
然后我們再了解下一步:
ac自動機,就是在tire樹的基礎上,增加一個fail指針,如果當前點匹配失敗,則將指針轉移到fail指針指向的地方,這樣就不用回溯,而可以路匹配下去了.(當前模式串后綴和fail指針指向的模式串部分前綴相同,如abce和bcd,我們找到c發現下一個要找的不是e,就跳到bcd中的c處,看看此處的下一個字符(d)是不是應該找的那一個)
一般,fail指針的構建都是用bfs實現的
首先每個模式串的首字母肯定是指向根節點的(一個字母你瞎指什么指,指了也是頭字母有什么用嘛)
現在第一層bfs遍歷完了,開始第二層
(根節點為第0層)第二層a的子節點為s,但是我們還是要從a-z遍歷,如果不存在這個子節點我們就讓他指向根節點(如下圖紅色的a)
當我們遍歷到s的時候,由於存在s這個節點,我們就讓他的fail指針指向他父親節點(a)的fail指針指向的那個節點(根)的具有相同字母的子節點(第一層的s),也就是這樣
按照相同規律構建第二層后,到了第三層的h點,還是按照上面的規則,我們找到h的父親節點(s)fail指針指向的那個位置(第一層的s)然后指向它所指向的相同字母根->s->h的這個鏈的h節點,如下圖
完全構造好后的樹
然后匹配就很簡單了,這里以ashe為例
我們先用ash匹配,到h了發現:誒這里ash是一個完整的模式串,好的ans++,然后找下一個e,可是ash后面沒字母了啊,我們就跳到hfail指針指向的那個h繼續找,還是沒有?再跳,結果當前的h指向的是根節點,又從根節點找,然而還是沒有找到e,程序END
過程如下圖