字符串匹配算法原理講解
0.引言
字符串的定位操作通常稱作字符串的模式匹配,是各種字符串處理系統中最重要的操作之一,本文介紹Hash、KMP、BM、Sunday四種匹配算法。
1. 字符串Hash
字符串Hash就是在字符串上進行哈希,可通俗理解爲把字符串轉爲整數,最後構建理想狀態下的一個整數對應一個字符串的單射。
給定一個字符串S,我們規定:
1.1 自然溢出法
自然溢出Hash公式爲:
這裏的hash數組利用了unsigned long long的自然溢出對取模。
1.2 單Hash法
單hash公式爲:
其中p和mod均爲素數,且p<mod,爲了降低hash衝突,可讓p和mod儘量往大取。
當一個哈希值對應兩個或多個字符串時出現哈希衝突
取素數可以有效避免衝突,可以參照以下例子:
設函數表達式爲
當N = 8,y = 2時:
當N = 7,y = 2時:
可以看出取素數衝突變少了。
1.3 雙Hash法
將一個字符串用不同的mod hash兩次,再將兩個結果用一個二元組表示,構成一個到字符串的理想單射,公式爲:
1.4字符串hash的獲取
根據定義可以得出:
又由
可以得出:
所以得出通式爲:
又因爲每次都要取模,並且做減法時有可能出現負數,所以對其加mod後再取餘做出修正,得到求字符串hash的最終公式爲:
2. KMP
字符串查找算法(Knuth-Morris-Pratt ),簡稱爲KMP算法,常用於在一個文本串S內查找一個模式串P 的出現位置,時間複雜度爲O(n+m),其中n爲文本串的長度,m爲模式串的長度。
KMP算法需要先尋找模式串中最長相等的前綴和後綴lps。
假設模式串P爲: “bcdebcd”
可找到的前綴prefix有: “b”、“bc”、“bcd”、“bcde”
可找到的後綴suffix有: “d”、“cd”、“bcd”、“ebcd”
可得出lps爲: “bcd”
因此可以根據模式串的lps來建立dp數組儲存字符串再次出現的位置:
設文本串S爲"bcbcdbcdbcbcbce",模式串P爲"bcbce",模式串的dp數組爲[0,0,1,2,0],匹配過程如下:
step1:
此時S串的i位字符與P串的j+1位字符匹配,i和j分別前進一位。
step2:
依次進行匹配…
step5:
此時S串的i位字符爲"d",P串的j+1位字符爲"e",匹配失敗,根據dp數組,字符"c"在P串的2號位出現過,j要從4號位回退到2號位,所以P串向右移動2位,i不變。
step6:
此時S串的i位字符"d"與P串的j+1位字符"b"失配,因爲dp數組中字符"c"在之前沒有出現過,則j要回退到0號位,所以P串向右移動2位,i不變。
step7:
接上一步,S串的i位字符與P串的j+1位字符失配,因爲j已經回退到了0號位,不能再回退,所以讓i前進一位。
step8:
再依次進行匹配
step9:
step10:
此時S串的i位字符爲"d",P串的j+1位字符爲"b",匹配失敗,根據dp數組要讓j回退到0號位,所以模式串向右移動2位,i不變。
step11:
根據規則再依次進行匹配
step12:
…
step19:
此時S串的i位字符爲最後一個字符"e",P串的j位於的4號位,i位字符與j+1位字符匹配,則i和j分別再前進一位。
step20:
此時i已超出S串的邊界,j位於P串的5號位,且等於P串的長度,則整個字符串匹配成功。
如果我們把匹配轉移和失配轉移用有限狀態自動機(FSM)的形式來表示,根據模式串bcbce的dp數組[0,0,1,2,0]可以得出下面一個狀態轉移圖:
注意: 這裏進行文本串遍歷的i與BF算法(即暴力(Brute Force)算法)進行文本串遍歷的i不同,BF算法進行文本串遍歷的i是指起始位置,KMP算法進行文本串遍歷的i是指當前匹配到的位置。
3. BM
BM(Boyer-Moore)算法則與KMP算法不同,KMP算法的模式串是從前往後遍歷,BM算法的模式串是從後往前遍歷。BM算法的時間複雜度最好爲O(n/m),最壞爲O(nm),其中n爲文本串的長度,m爲模式串的長度。BM算法有兩條規則,分別是壞字符串規則(Bad character rule)和好前綴規則(Good suffix rule),根據這兩條規則相互競爭來進行字符串匹配。
3.1 壞字符串規則
當文本串中的某個字符跟模式串的某個字符不匹配時,我們稱文本串中的這個失配字符爲壞字符,此時模式串需要向右移動,移動的位數等於壞字符在模式串對應的位置減去壞字符在模式串中最右出現前的位置(注意該字符要在失配的字符位置之前)。此外,如果"壞字符"不包含在模式串之中,則將模式串直接整體對齊到這個字符的後方,繼續比較。壞字符針對的是文本串。
設文本串S爲"GCTTCTGCTACCTTTTGCGC",模式串P爲"CCTTTTGC",壞字符串匹配規則如下:
當S串中有對應的壞字符"C"時,讓P串中最靠右的與壞字符相配的字符"C"與S串中的壞字符對齊,P串相應的向右移動3位(壞字符"C"在P串中的位置4減去壞字符在P串中最右出現的位置1等於3)。
step1:
如果P串中沒有出現S串中的那個壞字符,則將模式串直接整體對齊到這個字符的後方,繼續比較。
step2:
移動之後整個字符串匹配成功。
step3:
3.2 好後綴規則
當字符失配時,後移位數等於好後綴在模式串中的位置減去好後綴在模式串上一次出現的位置,且如果好後綴在模式串中沒有再次出現,則爲-1。好後綴針對的是模式串。
設文本串S爲"CGTGCCTACTTACTTACTTA",模式串P爲"CTTACTTAC",好後綴匹配規則如下:
當P串中存在已經匹配成功的好後綴"TAC",因爲要把目標串(上一個"TAC")對齊到好後綴現在的位置,所以P串向右移動的位數爲好後綴在P串中的位置6減去好後綴在P串中上一次出現的位置2等於4,然後從P串的最尾元素開始往前匹配。
step1:
根據規則進行匹配
step2:
移動之後整個字符串匹配成功。
step3:
BM算法結合了壞字符串規則(bc)與好後綴規則(gs)來進行遍歷,每一步對比兩個規則可跳過的字符數,跳過字符數多的則爲此步採用的策略,下一步模式串再從右向左依次與文本串相應位置的字符匹配,注意文本串是從左往右遍歷,模式串是從右往左遍歷。
設文本串S爲"GTTATAGCTGATCGCGGCGTAGCGGCGAA",模式串P爲"GTAGCGGCG",BM算法匹配過程如下:
初始時刻,P串的最後一個字符與S串對應位置的字符失配,如果採用bc可跳過6個字符,而採用gs則不能跳過字符,所以採用bc將P串向右移動,使模式串中與壞字符相配的字符與壞字符對齊。
step1:
此刻P串中的最後三個字符"GCG"與S串對應位置的字符匹配,但是到P串的第四個字符則與S串對應的字符失配,如果此時採用bc不能跳過字符,P串只能向右移動1位,但是採用gs可以發現P串中有以之對應的好後綴"GCG",此時可以將前一個"GCG"向後移動到現有"GCG"的位置,跳過的字符數爲2位,所以採用gs來移動P串。
step2:
接上一步之後,P串中的最後六個字符"GCGGCG"與S串對應位置的字符匹配,但是到P串的第七個字符"A"與S串對應位置的字符"C"失配,此時如果採用bc,因爲失配的P串位置之前已經沒有了與S串中壞字符"C"相配的字符,所以要將整個P串移動到壞字符之後的位置,只跳過了2個字符,但是採用gs則可以跳過7個字符,因爲P串中失配的字符位置之前只有一個與好後綴對應的字符"G",所以要將P串開頭的"G"對齊到現有最末尾"G"的位置,整體位置移動跳過了7個字符,所以此步採用gs來移動P串。
step3:
移動之後整個模式串匹配成功。
step4:
壞字符串規則和好後綴規則可以分開來採用,一般去除壞字符串規則,因爲如果字符串很長的話,採用壞字符串規則內存開銷非常大。
4. Sunday
Sunday算法的思想和BM算法很相似,只不過Sunday算法中模式串是從左往右匹配,在匹配失敗時關注的是主串中參加匹配的最末位字符的下一位字符,規則爲:
- 如果該字符沒有在模式串中出現則直接跳過,移動位數等於模式串長度加1
- 如果該字符在模式串中出現,其移動位數等於模式串長度減去該字符在模式串中最後出現的位置(模式串中字符位置從0開始累加)。
Sunday算法需要建立一個偏移表,偏移表的作用是存儲每一個字符的偏移量。設P爲模式串,m爲模式串長度,w爲字符,則偏移量計算公式爲:
例如P=“leetcode”:
m=8
shift[l] = 8 - max(l的位置) = 8 - 0 = 8
shift[e] = 8 - max(e的位置) = 8 - 2 = 6
shift[t] = 8 - max(t的位置) = 8 - 3 = 5
shift[c] = 8 - max(c的位置) = 8 - 4 = 4
shift[o] = 8 - max(o的位置) = 8 - 5 = 3
shift[d] = 8 - max(d的位置) = 8 - 6 = 2
shift[其他] = m + 1 = 8 + 1 = 9
設文本串S爲"ATTAAGGCACATAC",模式串P爲"ACAT",模式串中字符的偏移量爲:
shift[A] = 4 - max(A的位置) = 4 - 2 = 2
shift[C] = 4 - max(C的位置) = 4 - 1 = 3
shift[T] = 4 - max(t的位置) = 4 - 3 = 1
Sunday算法匹配過程如下:
step1:
此時P串的第2個字符"C"與S串的第2個字符"T"失配,關注S串中參加匹配的最末位字符的下一位字符"A",此時P串中有字符"A",且最後出現在2號位,偏移量爲P串的長度4減去字符"A"在P串中最後出現的位置2等於2位,所以將P串向右移動2位。
step2:
移動之後P串的第1個字符"A"與S串對應位置的字符"T"失配,關注S串中參加匹配的最末位字符的下一位字符"G",但因爲P串中沒有字符"G",偏移量爲P串的長度4加1等於5位,所以P串向右移動5位。
step3:
移動之後P串的第1個字符"A"與S串對應位置的字符"C"失配,關注S串中參加匹配的最末位字符的下一位字符"A",此時P串中有字符"A",且最後出現在2號位,偏移量爲P串的長度4減去2等於2位,所以將P串向右移動2位。
step4:
移動之後整個字符串匹配成功。
字符串匹配儘量先寫KMP,如果KMP被卡了,就寫Hash,如果Hash還不行,就試試BM。雖然Sunday算法很快,但是最常用的還是KMP和Hash。
推薦閱讀:
[1] 數據結構與算法 | 二分查找:劍指offer53 在排序數組中查找數字
[2] 數據結構與算法 | 數據結構中到底有多少種“樹”?一文告訴你
[3] 數據結構與算法之美 | 二分查找:劍指offer53 在排序數組中查找數字
[4] Charmve Coding | Codility - Counting Elevator Movements
[5] Charmve Coding | the smallest positive integer that does not occur in Array A
[7] 數據結構與算法之美 | 別怕,有我!KMP 算法詳解
關注微信公衆號:邁微電子研發社,獲取更多精彩內容。
知識星球 (付費羣):社羣旨在分享AI算法崗的秋招/春招準備攻略(含刷題)、面經和內推機會、學習路線、知識題庫等。