Rabin-Karp算法详解和实现(python)

  Rabin-Karp算法总的来说,一句话可以概况,就是一种利用对字符串进行哈希(hash)来解决字符串匹配问题的算法。所以该算法的特点就呼之欲出了,如何对字符串进行hash呢?
  这里首先对字符串匹配问题做一个简单的概述。字符串匹配问题可以简单描述成下述形式:

  • Input:一段字符串a,和一个字符串b
    Output:如果b中含有a,那么输出True,如果没有,输出False

什么是Hash

  那么最重要的问题来了,如何对字符串进行hash呢?首先我们需要明白hash的含义,hash的含义就是通过一个函数将一个值域(初始值)映射到另一个值域(散列值),这个函数就是哈希函数,也叫散列函数。这么说有点抽象,举个栗子,对python有一定了解的都知道,python的字典就是一种hash存储模式,一般我们很难保证有一整块连续的内存来存储数据,因此会将多个数据分散存储在多个地方,但是这样不方便查找,如果我们有三个数据(key),x1,x2,x3(三个数据必须不相等,相等的数据拥有相同的hash地址),我们的hash函数是H(x) = 10*x + 5,假设我们从地址为1,2,3开始来存储这三个数据,那么我们得到的最后的地址就是15,25,35,也就是说我们通过x就可以直接得到地址,而不需要一个个去遍历查找,极大的减少了查询时间。

什么是Hash冲突

  细心的同学就发现了,如果我们的H(x)比较复杂,如果H(x) = x2 + 5,那当我们的key1 = -1, key2 = 1,他们的hash地址不就相等了么?事实上,的确是的,对于key1 != key2,但是通过H(x)算出来的hash值是一样的,这就是Hash冲突。避免hash冲突的方法有很多,比如开放地址法(再散列法)、链地址法(拉链法)、再哈希法、建立公共溢出区等等,这里就不细讲了,因为都用不着。

Rabin-Karp算法

  到这里,我们的基础知识就基本介绍完了,可以正式进入Rabin-Karp算法的介绍和实现了。我们比较两个字符串是不是匹配(相等),一般可以从头往后进行遍历,比较每一个对应位置的字符是不是相等,直到比较完所有位置或者存在对应位置字符不等。将问题提高一点,判断字符串a是不是b的子串,大家很容易想到滑动窗口,如果a的长度是m,那么滑动窗口的长度也就是m,滑动的次数是(n-m),比较的次数是m次,所以传统暴力解法的时间复杂度为(m-n)*m。
  Rabin-Karp算法其实也是基于滑动窗口来进行比较,只是它不是比较每个字符,而是比较这整个m长子串的hash值,这样无需遍历,一次比较即可完成,所有的时间复杂度是,m(构建a字符串的hash值所需要的时间复杂度) + n(遍历字符串n所需要的时间复杂度)。

对字符串进行hash

  对字符串hash也很简单,每一个字符在ASCII中均有一个独属于自己的编码比如a-z对应的编码是97-122,A-Z对应的编码是65-90。有了这些初始值,那么剩下的就是我们哈希函数的建立了。我们以26个大写字符串为例,讲解其hash函数的构建,我们发现对于长度为m的字符串,其可能组合为26m,hash函数的目的是尽可能为每一个初始值找到一个唯一的散列值,也就是说我们的hash函数的散列区间也需要满足26m,这里我们很容易就考虑到进制,即如果只有2种状态我们就考虑用2进制,10种状态就考虑用十进制,本例26种状态,我们就可以考虑用26进制来表示,举个栗子,例如我们对字符串ABCD进行哈希:

  • A-Z对应的编码是65-90,将其对应到0-25之间(表示26进制),因此A、B、C、D对应的值为0、1、2、3
    1 : H(“ABCD”) = 0*263 + 1*262 + 2*261 + 3*260= 731

  这就是字符串hash的方法,那么我们如何字符串匹配时来使用呢?难道每次的m长度窗口都要计算一次么?那时间复杂度岂不是就是O(mn)了?
  当然不是,这里用了前缀和的思想,对于"ABCDEF",其对应的编码是012345,我们滑动窗口长度为3,那么当我们计算完"ABC"的hash值了之后,我们需要计算"BCD"的hash值,由于每一个窗口的hash值实质上是一个累计和,所以我们只需要对H(“ABC”)减去A的贡献,对剩下的值乘以26,最后再加上D的贡献,就可以了,这样的时间复杂度为1。其中A的贡献为0*26m-1 (m为滑动窗口长度),C对应的贡献为 3*260
  算法原理都清楚了,接下来就是优化,即如何避免Hash冲突,首先我们要明白只要hash函数选的不是很离谱,发生冲突的概率是很低的,因此对于本题的字符串匹配问题,当hash值相等时,我们再同时比较字符串,这样时间复杂度会高一点,但是完全避免了hash冲突会引起的匹配错误。代码如下所示:

def search(a, b):
    """
    判断a是不是b的子串
    >>>search( "AABA", "ACAADAABAAAAABABAA")
    >>>True
    """
    hashA, hashB = 0, 0
    aN = [ord(c)-ord('A')+1 for c in a]
    bN = [ord(c)-ord('A')+1 for c in b]
    m, n = len(aN), len(bN)
    for i in range(m):
        hashA = 26*hashA + aN[i]
        hashB = 26*hashB + bN[i]
    for j in range(m-1, n):
        if j > m-1:
            hashB -= bN[j-m]*(26**(m-1))
            hashB = hashB*26 + bN[j]
        if hashB == hashA:
            if a == b[j-m+1:j+1]:
                return True
    return False
search( "AABA", "ACAADAABAAAAABABAA")
  • 如果大家觉得有帮助,欢迎点个免费的赞,谢谢!在这里插入图片描述
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lemon_tttea

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值