KMP算法go语言

1.1 Next数组:

最大公共前后缀

img

next数组:

KMP算法go语言_第1张图片

算法:

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
}

1.2 Next数组优化----NextVal数组

如果用之前的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
}

1.3 KmpSearch

根据模式串“ABCDABD”的 next 数组可知失配位置的字符 D 对应的 next 值为 2,代表字符 D 前有长度为 2 的相同前缀和后缀(这个相同的前缀后缀即为“AB”),失配后,模式串需要向右移动 j - next [j] = 6 - 2 =4 位。

KMP算法go语言_第2张图片

向右移动 4 位后,模式串中的字符 C 继续跟文本串匹配。

KMP算法go语言_第3张图片

算法:

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
}

BM算法

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"的后一位。

img

2. 依然从尾部开始比较,发现"P"与"E"不匹配,所以"P"是"坏字符"。但是,"P"包含在模式串"EXAMPLE"之中。因 为“P”这个“坏字符”对应着模式串的第6位(从0开始编号),且在模式串中的最右出现位置为4,所以,将模式串后移6- 4=2位,两个"P"对齐。

img

img

3. 依次比较,得到 “MPLE”匹配,称为"好后缀"(good suffix),即所有尾部匹配的字符串。注意,“MPLE”、“PLE”、“LE”、"E"都是好后缀。

img

4. 发现“I”与“A”不匹配:“I”是坏字符。如果是根据坏字符规则,此时模式串应该后移2-(-1)=3位。问题是,有没有更优的移法?

img

img

  1. 更优的移法是利用好后缀规则:当字符失配时,后移位数 = 好后缀在模式串中的位置 - 好后缀在模式串中上一次出现的位置,且如果好后缀在模式串中没有再次出现,则为-1。
    所有的“好后缀”(MPLE、PLE、LE、E)之中,只有“E”在“EXAMPLE”的头部出现,所以后移6-0=6位。
    可以看出,“坏字符规则”只能移3位,“好后缀规则”可以移6位。每次后移这两个规则之中的较大值。这两个规则的移动位数,只与模式串有关,与原文本串无关。

img

6. 继续从尾部开始比较,“P”与“E”不匹配,因此“P”是“坏字符”,根据“坏字符规则”,后移 6 - 4 = 2位。因为是最后一位就失配,尚未获得好后缀。

img

你可能感兴趣的:(KMP算法go语言)