字符串匹配 - Sunday算法

背景

提起字符串匹配,可能很多人都会想到KMP算法 O(m+n),但是其实KMP并不常用,因为依然是慢的,常用的其实是BM算法 O(m/n)(Boyer-Moore算法),这就是很多文本编辑器的查找功能采用的算法,而Sunday算法是在其之上又做了一些改动。下面我给大家简单介绍下Sunday算法与BM算法针对于KMP算法的差异点。

思路

先说下BM算法

首先的区别,与KMP从前往后扫描模式串不同,BM算法是从后往前对模式串进行扫描与主串进行匹配的
其核心就是两个启发策略:

(1)坏字符算法
当出现一个坏字符时, BM算法向右移动模式串, 让模式串中最靠右的对应字符与坏字符相对,然后继续匹配。坏字符算法有两种情况。

Case1:模式串中有对应的坏字符时,让模式串中最靠右的对应字符与坏字符相对(PS:BM不可能走回头路,因为若是回头路,则移动距离就是负数了,肯定不是最大移动步数了),如下图。


字符串匹配 - Sunday算法_第1张图片
Case1

Case2:模式串中不存在坏字符,那么直接右移整个模式串长度这么大步数,如下图。

字符串匹配 - Sunday算法_第2张图片
Case2

(2)好后缀算法
如果程序匹配了一个好后缀, 并且在模式串中还有另外一个相同的后缀(参见case1)或后缀的部分(参见case2), 那把下一个后缀或部分移动到当前后缀位置。即,模式串的后u个字符和主串都已经匹配了,但是接下来的一个字符不匹配,如果说后u个字符在模式串其他位置也出现过或部分出现,我们将模式串右移到前面的u个字符(参见case1)或部分和最后的u个字符或部分相同(参见case2)的位置,如果说后u个字符在模式串其他位置完全没有出现,那么就直接右移整个模式串(参见case3),如下图所示:

Case1:模式串中有子串和好后缀完全匹配,则将最靠右的那个子串移动到好后缀的位置继续进行匹配。

字符串匹配 - Sunday算法_第3张图片
Case1

Case2:如果不存在和好后缀完全匹配的子串,则在好后缀中找到具有如下特征的最长子串,使得P[m-s…m]=P[0…s]。

字符串匹配 - Sunday算法_第4张图片
Case2

Case3:如果完全不存在和好后缀匹配的子串,则右移整个模式串。

(3)移动规则
BM算法的移动规则是:
BM算法是每次向右移动模式串的距离是 MAX(shift(好后缀),shift(坏字符)),即按照好后缀算法和坏字符算法计算得到的最大值。

缺点

BM算法与KMP思想类似,但是更好了利用了预处理数组,使得更有效率,但是模式串的预处理数组也变得相对更加复杂,所以当面临较小场景时,并不一定适合选用BM算法。

优点
  • 时间复杂度
KMP O(m+n)
BM O(m/n) - O(m*n)
  • 思维优势
    BM算法从后往前扫描模式串使得它更好的利用了“后缀”,BM算法的启发策略也使得模式串可以更加有效率的移动,在面临实际使用中的较大匹配场景时,效率提高明显。

BM算法的具体实现,例如好后缀算法和坏字符算法 模式串的预处理数组如何实现?
请移步某前辈的文章: grep之字符串搜索算法Boyer-Moore由浅入深,这里不细说了,只介绍下核心差异点。

知道了BM算法,下面我们来说Sunday算法

Sunday算法

Sunday算法是从前往后扫描模式串的,其思路更像是对于“坏字符”策略的升华。关注的是主串中参与匹配的最末字符(并非正在匹配的)的下一位,稍后看图更容易理解
Sunday算法只有一个启发策略

Case1:当遇到不匹配的字符时,如果关注的字符没有在模式串中出现则直接跳过
移动位数 = 子串长度 + 1;

字符串匹配 - Sunday算法_第5张图片
Case1

Case2: 当遇到不匹配的字符时,如果关注的字符在模式串中也存在时,其
移动位数 = 模式串长度 - 该字符最右出现的位置(以0开始)
或者移动位数 = 模式串中该字符最右出现的位置到尾部的距离 + 1
看下例子:

字符串匹配 - Sunday算法_第6张图片
Case2

策略是很清晰简单的,但是想一下就可以发现,这个算法缺点也很明显:

缺点

如果是下面这个情况,会怎么样?
主串:baaaabaaaabaaaabaaaa
子串:aaaaa
没错,这个时候,效率瞬间变成了O(m*n)
Sunday算法的移动是取决于子串的,这一点跟BM算法没什么区别,当这个子串重复很多的时候,就会非常糟糕了。大家知道这一点,有所取舍就好

优点
  • 时间复杂度
KMP O(m+n)
BM O(m/n) - O(m*n)
Sunday O(m/n) - O(m*n)
实际使用中 Sunday算法比BM算法略优
  • 思维优势
    我理解Sunday算法优在,它的思路很简单,很清晰,而现实生活的实际场景中,又恰恰能较好的规避它的缺点,使得它能够大大增加匹配效率。

代码

  • 实例:LeetCode.28 - 实现strStr()
def sunday_search(self, haystack: str, needle: str) -> int:
        if not needle:
            return 0

        offset = {}
        n_l = len(needle)

        for i in range(n_l):
            offset[needle[i]] = n_l - i

        i, j, h_l = 0, 0, len(haystack)

        while i <= h_l - n_l:
            j = 0

            while haystack[i + j] == needle[j]:
                j += 1
                if j == n_l:
                    return i

            if i + n_l == h_l:
                return -1

            if haystack[i + n_l] in offset:
                i += offset[haystack[i + n_l]]
            else:
                i += n_l + 1

        return -1

以上,欢迎大家讨论,共同进步


欢迎大家关注我的公众号


半亩房顶

你可能感兴趣的:(字符串匹配 - Sunday算法)