最大公共前后缀
next数组:
算法:
func NextArray(needle string) []int {
l := len(needle)
next := make([]int, l)
next[0] = -1
k := -1
i := 0
for i < l-1 {
if k == -1 || needle[k] == needle[i] {
i++
k++
next[i] = k
} else {
k = next[k]
}
}
return next
}
如果用之前的next 数组方法求模式串“abab”的next 数组,可得其next 数组为-1 0 0 1(0 0 1 2整体右移一位,初值赋为-1),当它跟下图中的文本串去匹配的时候,发现b跟c失配,于是模式串右移j - next[j] = 3 - 1 =2位。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9sEvtvZ5-1584521838818)(/Users/lx/Downloads/8394323_1308075859Zfue.jpg)]
右移2位后,b又跟c失配。事实上,因为在上一步的匹配中,已经得知p[3] = b,与s[3] = c失配,而右移两位之后,让p[ next[3] ] = p[1] = b 再跟s[3]匹配时,必然失配。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wrWtcIeu-1584521838819)(/Users/lx/Downloads/8394323_13080758591kyV.jpg)]
问题出在不该出现p[j] = p[ next[j] ]。为什么呢?理由是:当p[j] != s[i] 时,下次匹配必然是p[ next [j]] 跟s[i]匹配,如果p[j] = p[ next[j] ],必然导致后一步匹配失败(因为p[j]已经跟s[i]失配,然后你还用跟p[j]等同的值p[next[j]]去跟s[i]匹配,很显然,必然失配),所以不能允许p[j] = p[ next[j ]]。如果出现了p[j] = p[ next[j] ]咋办呢?如果出现了,则需要再次递归,即令next[j] = next[ next[j] ]。
修改后求NextVal算法:
func NextValArray(needle string) []int {
l := len(needle)
next := make([]int, l)
next[0] = -1
k := -1
i := 0
for i < l-1 {
if k == -1 || needle[k] == needle[i] {
i++
k++
if needle[i] != needle[k] {
next[i] = k
} else {
next[i] = next[k]
}
} else {
k = next[k]
}
}
return next
}
根据模式串“ABCDABD”的 next 数组可知失配位置的字符 D 对应的 next 值为 2,代表字符 D 前有长度为 2 的相同前缀和后缀(这个相同的前缀后缀即为“AB”),失配后,模式串需要向右移动 j - next [j] = 6 - 2 =4 位。
向右移动 4 位后,模式串中的字符 C 继续跟文本串匹配。
算法:
func KmpSearch(haystack string, needle string, next []int) int {
l1 := len(haystack)
l2 := len(needle)
i, j := 0, 0
for i <= l1 && j < l2 {
if j == -1 || haystack[i] == needle[j] {
i++
j++
} else {
j = next[j]
}
}
if j == l2 {
return i - l2
}
return -1
}
KMP的匹配是从模式串的开头开始匹配的,而1977年,德克萨斯大学的Robert S. Boyer教授和J Strother Moore教授发明了一种新的字符串匹配算法:Boyer-Moore算法,简称BM算法。该算法从模式串的尾部开始匹配,且拥有在最坏情况下O(N)的时间复杂度。在实践中,比KMP算法的实际效能高。
BM算法定义了两个规则:
1.坏字符规则:当文本串中的某个字符跟模式串的某个字符不匹配时,我们称文本串中的这个失配字符为坏字符,此时模式串需要向右移动,移动的位数 = 坏字符在模式串中的位置 - 坏字符在模式串中最右出现的位置。此外,如果"坏字符"不包含在模式串之中,则最右出现位置为-1。
2.好后缀规则:当字符失配时,后移位数 = 好后缀在模式串中的位置 - 好后缀在模式串上一次出现的位置,且如果好后缀在模式串中没有再次出现,则为-1。
给定文本串“HERE IS A SIMPLE EXAMPLE”,和模式串“EXAMPLE”,现要查找模式串是否在文本串中,如果存在,返回模式串在文本串中的位置。
1. 首先,"文本串"与"模式串"头部对齐,从尾部开始比较。"S"与"E"不匹配。这时,"S"就被称为"坏字符"(bad character),即不匹配的字符,它对应着模式串的第6位。且"S"不包含在模式串"EXAMPLE"之中(相当于最右出现位置是-1),这意味着可以把模式串后移6-(-1)=7位,从而直接移到"S"的后一位。
2. 依然从尾部开始比较,发现"P"与"E"不匹配,所以"P"是"坏字符"。但是,"P"包含在模式串"EXAMPLE"之中。因 为“P”这个“坏字符”对应着模式串的第6位(从0开始编号),且在模式串中的最右出现位置为4,所以,将模式串后移6- 4=2位,两个"P"对齐。
3. 依次比较,得到 “MPLE”匹配,称为"好后缀"(good suffix),即所有尾部匹配的字符串。注意,“MPLE”、“PLE”、“LE”、"E"都是好后缀。
4. 发现“I”与“A”不匹配:“I”是坏字符。如果是根据坏字符规则,此时模式串应该后移2-(-1)=3位。问题是,有没有更优的移法?
6. 继续从尾部开始比较,“P”与“E”不匹配,因此“P”是“坏字符”,根据“坏字符规则”,后移 6 - 4 = 2位。因为是最后一位就失配,尚未获得好后缀。