滚动哈希:Karp-Rabin字符串匹配

应用:在大文本中寻找字符串

朴素的模式匹配思想

将模式s从文本t的开头位置匹配,不匹配则往后移一个位置,重新匹配,时间复杂度为 O ( ∣ s ∣ ∗ ( ∣ t ∣ − ∣ s ∣ ) ) O(|s|*(|t|-|s|)) O(s(ts)),约为 O ( ∣ s ∣ ∗ ∣ t ∣ ) O(|s|*|t|) O(st).

滚动哈希

考虑模式s本次匹配的子串与下一次匹配的子串的区别,仅仅在于移除了它的头以及添加了一个尾,如果能有效利用这个特点来计算字符串的哈希值,使得中间部分不变时,哈希值的变化只与首尾的变化有关,那么计算下一次匹配的子串的哈希值时,只需要移除头部带来的影响以及增添尾部带来的影响即可,这些操作的时间复杂度都是 O ( 1 ) O(1) O(1)的。
考虑数据结构r,它维护了一个字符串x,这个r有如下操作:

  • r.append(c):追加字符c到x的尾部
  • r():计算x的哈希值
  • r.skip(c):删除x的首字符。我们认为删除的首字符等于c,而不是说从x中删除字符c。

用rs表示维护s的数据结构,用rt表示维护文本t的数据结构。
首先,将s的每个字符添加到rs中:

for c in s: rs.append(c)

然后从t的开始位置0处,添加一个长度为 ∣ s ∣ |s| s的子串:

for c in t[:len(s)]: rt.append(c)

现在,rs()就是s的滚动哈希值,rt()就是t的滚动哈希值。
然后就能开始匹配了:

if rs() == rt(). . . 
# 如果相等,待会儿再说
# 否则继续匹配
for i in range(len(s), len(t)):
	rt.skip(t[i - len(s)])  # 删除首字符
	rt.append(t[i])
	if rs() == rt():
		# s可能匹配上了,因为存在哈希碰撞
		check whether s == t[i - len(s) + 1: i + 1]
		if equal: 
			found match
		else:
			发生的可能性 <= 1/|s|	

整个匹配的时间复杂度为 O ( ∣ s ∣ + ∣ t ∣ + # m a t c h ∗ ∣ s ∣ ) O(|s| + |t| + \#match*|s|) O(s+t+#matchs)
#match是哈希值相等的次数,每一次都要检查是否匹配上了,这只需要花费常数时间。
那么哈希函数到底用的是什么呢?
我们把字符串视为一个多位数,基数为字母表的大小,如果是ASCII,那么
∣ ∑ ∣ = 256 |∑|=256 =256。添加、删除字符等价于一些四则运算,这些运算耗费常数时间。

示例

力扣题库第28题为例进行算法实现。

class Solution {
public:
    int strStr(string s, string t) {
        if (t == "")
        	return 0;
        int len1 = s.length(), len2 = t.length();
        if (len1 < len2)
        	return -1;
        unsigned long long hash1 = 0, hash2 = 0, base = 26, m = 1, M = (1ULL << 53) - 1;
        for (int i = 1; i < len2; i++)
            m = (m * base) % M; //计算最高位的权重,即26的(len2 - 1)次幂
        for (int i = 0; i < len2; i++) {
            hash1 = (hash1 * base + t[i] - 'a') % M;  //模式串的哈希值
            hash2 = (hash2 * base + s[i] - 'a') % M;  //目标串的哈希值
        }
        for (int i = 0; i <= len1 - len2; i++) {
            if (hash1 == hash2 && s.substr(i, len2) == t) //短路原则,如果哈希值不相等则不会匹配
                return i;
            hash2 = (hash2 - (s[i] - 'a') * m + M) % M;
            hash2 = (hash2 * base + (s[i + len2] - 'a')) % M;
        }
        return -1;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值