字串的模式匹配:Sunday 演算法
Sunday演算法是Daniel M.Sunday於1990年提出的字串模式匹配。其核心思想是:在匹配過程中,模式串發現不匹配時,演算法能跳過儘可能多的字元以進行下一步的匹配,從而提高了匹配效率。其效率在匹配隨機的字串時比其他匹配演算法還要更快。Sunday演算法的實現可比KMP,BM的實現容易太多。
要理解Sunday演算法,建議先閱讀《字串的模式匹配: BF演算法》、《字串的模式匹配:KMP演算法》、 《字串的模式匹配:BM演算法 》
時間複雜度:最差情況O(MN),最好情況O(N)
演算法思想:
在匹配過程中,先從左到右逐個字元比較,當模式串發現不匹配時,跳過儘可能多的字元以進行下一步的匹配,從而提高了匹配效率。
模式串向後位移:字串和模式串同時移動的長度 + 模式串的長度 - 該字元在模式串中出現的第一個位置(從右向左尋找模式串,如果尋找不到則為-1)。
開始的時候,讓字串”T”的第一個字元T[0]和模式串”P”的第一個字元P[0]匹配,如果不匹配。以字串出現在模式串後面的位置n,取T[n]字元,找到該字元在模式串字元的的位置k,並將模式串p[k]字元與T[n]對齊,即模式串向後位移n-k; 若在模式串中找不到T[n]字元,則將模式串p[0]與字串T[n]對齊,模式串向後以模式串長度位移。位移後再比對P[0]和T[N], 按照上述過程依此匹配。
下面還是看一下實際的匹配過程,道理可能講的是不夠清晰。
匹配過程:
假設有字串和模式串如下:
字串T: “Lessons tearned en software te”
模式串P:
模式串P的長度為8。字串T的長度為30。
1、首先字串T和模式串P首字元對齊。如下:
Lessons tearned en software te
software
2、然後T[0]和P[0]字元匹配,也就是”L”和”s”, 這個時候不匹配;根據Sunday演算法要求,以字串出現在模式串後面的位置n,取T[n]字元,找到T[n]即T[8]字元”t”位於模式串P中從後向前查找出現的第一個位置,即模式串的P[3]。模式串向後位移:字串和模式串同時移動的長度 + 模式串的長度 - 該字元在模式串中出現的第一個位置。 模式串移動位數 = 0 + 8 - 3 = 5位, 位移5位後T[8]與P[3]對齊後得到如下結果:
Lessons tearned en software te
software
3、比對T[5]和P[0],也就是”n”和”s”, 這個時候不匹配,再次尋找字串中在模式串後面的那個字元在模式串中出現的位置。也就是字串的tearned 中的”e”,並且尋找”e”在模式串出現的位置,也就是模式串的P[7]位置。此時模式串位移 = 0 + 8 - 7 = 1位。位移後得到結果:
Lessons tearned en software te
software
4、此時,對比T[6]和P[0],也就是”s”和”s”, 發現字元匹配。那麼字串和模式串同時向後移1位。 對比T[6+1]和P[0+1], 也就是字串的” “和模式串的”o”,發現 再次不匹配。找到字串在模式串後移1位T[6+1+8]的字元” “, 尋找字元”d”在模式串的位置,發現模式串中不存在d,也就是說。 模式串移動位數 = 1 + 8 - (-1) = 10, 移動結果如下:
Lessons tearned en software te
software
5、依此類推,直到找到匹配的位置,或者到達字串的末尾 。
再假設有字串和模式串如下:
字串T: “Lessonsotearned en software te”
模式串P: “software”
模式串T的長度為8。字串T的長度為30。
其匹配過程,第1、2、3步與上訴例子的1、2、3不一樣,執行後位移如下:
Lessonsotearned en software te
software
那麼如上對齊後,匹配字串T[6]與模式串P[0], 也就是”s”和”s”, 匹配一致。那麼字串T和模式串P後移1位,匹配字串T[6+1]與模式串P[0+1],也就是”o”和”o”, 匹配一致。那麼字串T和模式串P再次後移1位,匹配字串T[6+1+1]與模式串P[0+1+1],也就是”t”和”f”不一致。所以模式串移動位數 = 2 + 8 - 7 = 3, 得到結果如下:
Lessonsotearned en software te
software
直到找到匹配的位置,或者到達字串的末尾 。
具體實現(java):
/// <summary>
/// 通過Sunday查詢演算法,查詢text串中pattern字串的第一次出現的位置,沒查詢到返回-1
/// </summary>
/// <param name="text">目標(源)串</param>
/// <param name="pattern">匹配串</param>
/// <returns>int,返回第一次出現的索引.返回-1表示沒有找到.</returns>
public static int sundaySearch(char[] text, char[] pattern){
int i = 0, j = 0, k;/*分別記錄text索引,pattern索引還有,串匹配計數時索引*/
int tl, pl;/*分別記錄字串text和pattern的長度*/
int pe;/*分別記錄text匹配pattern最後的索引的下一個索引*/
int rev=-1;/*記錄返回的索引值,否則無法返回*/
/*非法情況,直接返回-1*/
if ((text == null) || (pattern == null) || (tl = text.length) < (pl = pattern.length))
return -1;
while (i < tl && j < pl) {
/* 匹配正確就僅繼續匹配 */
if (text[i] == pattern[j]) {
++i;
++j;
continue;
}
pe = i + pl;
/* 匹配失敗,移動i和j索引值,為下一次匹配做準備 */
if (pe >= tl) /* 當下一次的位置已經超過text的長度時,返回-1表示沒有找到 */
return -1;
for (k = pl - 1; k >= 0 && text[pe] != pattern[k]; --k)
;
i += (pl - k);// (pl - k)表示i需要移動的步長
rev = i;// 記錄當前的索引
j = 0;/* j重新開始 */
// System.out.println("總移動位數:" + rev);
}
return i <= tl ? rev : -1;
}