Rabin Karp算法 & 实现 strStr() 函数

36 篇文章 2 订阅
30 篇文章 0 订阅

实现 strStr() 函数

给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回  -1。

示例 1:

输入: haystack = "hello", needle = "ll"
输出: 2
示例 2:

输入: haystack = "aaaaa", needle = "bba"
输出: -1

来源:力扣(LeetCode)
链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

1、暴力法

字符串haystack 的长度为n,字符串needle 的长度为m。

  1. 在字符串t上,放一个长度为m窗口window。
  2. 从头开始慢慢的滑动这个窗口window,每滑动一次,就把窗口里的内容和p对比一下。
  3. 如果一样,就返回index。如果不一样,那么继续往右滑动一格窗口window。 

从上述算法可知,一共会有(n-m+1)个窗口滑动。->这一步的复杂度是O(n)

然后每次窗口滑动,都涉及到两个长度为m的字符串的比较。->这一步的复杂度是O(m)

由于这两部是一个nested loop,所以最终的算法复杂度是O(m*n)。

def violence1():
    if haystack == needle:
        return 0
    n = len(needle)
    for i in range(len(haystack) - n + 1):
        if haystack[i:i + n] == needle:
            return i
    return -1

而暴力法的另一种写法:str.index()

def violence2():
    return haystack.index(needle) if needle in haystack else -1

2、Rabin Karp算法

Rabin Karp基本思想和暴力破解算法是一样的。也需要一个大小为m的窗口,但是不一样的是,不是直接比较两个长度为m的字符串,而是比较他们的哈希值。

                          

同样的,共会有(n-m+1)个窗口滑动。->这一步的复杂度是O(n)

这个是不变的,但是由于哈希值都是数字,所以两个数字的比较,只需要O(1)。

如何计算哈希值的时间?

在一开始的时候,我们需要计算needle的哈希值,由于needle的长度为m,所以计算needle的哈希值的时间为O(m).

然后每一次移动窗口,都需要对窗口内的字符串计算哈希值,此时这个字符串的长度为m,所以计算它哈希值的时间也为O(m).如果照这样看,算法复杂度还是O(m*n),和上面的暴力破解算法没有任何区别。

但是实际上,计算移动窗口内的哈希值并不需要O(m),在已知前一个窗口的哈希值的情况下,计算当前窗口的哈希值,只需要O(1)的时间复杂度。

现在再来看上面提到的计算字符串哈希值的函数,假设现在窗口的起点在j这个位置,此时窗口内的字符串哈希值为:

                                          

那么,当计算下一个窗口的哈希值时,也就是当窗口的起点为j+1时,哈希函数值可由如下方法计算:

                            

所以,这样看来,在计算出第一个窗口的函数值之后,后面的每一个窗口哈希值都可以根据上述公式计算,只需要做一次减法,一次乘法,一次加法。所以之后的每一次哈希值计算都是O(1)的复杂度。

总的时间复杂度就变成了O(n+m)。

KMP算法的时间复杂度同样是(m+n),由于太复杂不容易掌握,所以还是Rabin Karp算法比较好。

注意:hashCode相等时字符串相等的必要条件,即,

  • 两个字符串的hashCode不一样,两个字符串一定不一样
  • 两个字符串的hashCode不】一样,两个字符串不一定一样

当模式长度太大时, 我们使用Horner方法计算模式字符串的散列值:

def cal_hash(s):
    BASE = 10 ** 6
    res_hash = 0
    for i in range(len(s)):
        res_hash = (31 * res_hash + ord(s[i])) % BASE

    return res_hash

接下来完整的代码:

def rabin_karp():
    BASE = 1000000
    m = len(needle)
    if m == 0:
        return 0
    # 计算31^m
    power = 1
    for i in range(m):
        power = (power * 31) % BASE

    target_code = 0
    for i in range(m):
        target_code = (target_code * 31 + ord(needle[i])) % BASE

    hash_code = 0
    for i in range(len(haystack)):
        hash_code = (hash_code * 31 + ord(haystack[i])) % BASE
        if i < m - 1:  # 不够needle长度
            continue
        if i >= m:
            hash_code = (hash_code - ord(haystack[i - m]) * power % BASE)
            if hash_code < 0:  # hash_code为负数时候要+BASE(一个就可以)
                hash_code = haystack + BASE
        if hash_code == target_code and haystack[i - m + 1:i + 1] == needle:  # 特殊情况hash(abc)
            return i - m + 1
    return -1

实际上,在python强大的内置函数下,并没有快多少,,,只是熟悉一下hashCode的计算方法,以后用到更合适的地方。
 

————————————————

参考:

Rabin-Karp算法:字符串匹配问题_字符串错误率_Fiona_ll的博客-CSDN博客

Rabin-Karp字符串查找算法-梦醒潇湘love-ChinaUnix博客

https://www.cnblogs.com/golove/p/3234673.html

horner法则求字符串散列值_zhongkeli的博客-CSDN博客

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值