字符串匹配——Rabin-Karp algorithm(一)

本文深入探讨了Rabin-Karp算法,一种利用哈希函数加速字符串匹配过程的方法。通过将字符串转换为数值,算法能在文本中快速查找模式串,尽管存在哈希碰撞的风险,但通过精心设计的哈希函数和滚动哈希技术,能有效减少不必要的比较,提升匹配效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        在https://blog.youkuaiyun.com/To_be_to_thought/article/details/84679263这篇使用的是朴素的字符串匹配方法,算法复杂度为O(mn)(m为模式串长度,n为文本串长度)。在字符串匹配任务中,Rabin–Karp算法通过hash函数试图加速文本子串与模式串的匹配过程的“逐一比对过程”

       这里的hash函数可以将每个子串转化为一个数值,因为存在这样一个事实:如果两个字符串是相等的,则哈希(函数)值相等,字符串匹配问题也就被归化为“计算模式串的哈希值,然后在文本串中寻找相等哈希值的子串”的问题。

       然而问题来了,一些不同的字符串可能有相同的哈希值,相同的哈希值的两字符串可能不匹配,因此文本串中“潜在的匹配字符串”与模式串还需要一一比对来再次确认,那么对于越长的“潜在匹配子字符串”需要花费更多的时间来再次确认。

伪代码如下:

1 	RabinKarpSearch(string s[1..n], string pattern[1..m])
2		hpattern := hash(pattern[1..m]);     		O(m)
3			for i from 1 to n-m+1
4				hs := hash(s[i..i+m-1])				O(m)
5				if hs = hpattern						O(n)
6					if s[i..i+m-1] = pattern[1..m]		O(m)
7						return i
8		return not found

       第五行要执行n-m+1次,每次比较都是O(1)。朴素的计算字符串S[i+1…i+m]哈希值的方法需要O(m)次,因为哈希值计算在每次循环中都要进行,朴素哈希计算方法需要O(mn)次,为了加速整个算法,哈希值计算必须变成常数时间内完成,一个小技巧就是变量hs已经包含先前字符串s[i..i+m-1]的哈希值。如果这个值被用来在常数时间内计算下一个字符串的哈希值,那么连续计算哈希值就非常的快,可以使用rolling hash来实现这一想法,一个简单的但不是很好的rolling hash 函数仅仅加上子串中每个字符的值:hash(s[i+1…i+m])=hash(s[i…i+m-1])-s[i]+s[i+m],这个简单的函数计算哈希值使得产生哈希碰撞的概率提高从而导致line5执行较多,好的性能需要良好的哈希函数来减少line5的执行,因为逐个字符的比对需要O(m)次,整个算法的最坏执行复杂度为O(mn)。所以这个hash函数的设计关乎整个算法的性能

下面介绍一种哈希函数,它将每个字符串视作以某个很大的素数为基的数

比如基底是256,素数模是101,字符串’hi’(104,105)的哈希值计算为:

                                                  

朴素的Rabin–Karp算法代码如下:

class Solution {
    public static int base=256;
    public static int module=101;
    
    public static boolean match(String str1,String str2)
    {
        assert str1.length()==str2.length();
        for(int i=0;i<str1.length();i++)
        {
            if(str1.charAt(i)!=str2.charAt(i))
                return false;
        }
        return true;
    }
    
    public static int hash(String str)
    {
        int hash=0;
        for(int i=0;i<str.length();i++)
            hash=(hash*base+str.charAt(i))/module;
        return hash;
    }
    
    public int strStr(String haystack, String needle) {
        if(needle=="" || needle.length()==0)
            return 0;
        int n=haystack.length(),m=needle.length();
        int targetHash=hash(needle);
        for(int i=0;i<n-m+1;i++)
        {
            String str=haystack.substring(i,i+m);
            if(hash(str)==targetHash)
                if(match(str,needle))
                    return i;
        }
        return -1;
    }
}

       因为这个算法的最坏复杂度O(mn),单个字符串的匹配性能劣于KMP算法和BM算法,然而在多字符串匹配应用上表现很好。为了在文本串中寻找k(一个较大的数字)个定长(长度都为m)模式串,RK算法的一个变体使用一个布隆过滤器或者集合数据结构来检查:给定字符串的哈希值是否属于“模式串哈希值集合”(要寻找的模式串们)。

伪代码如下:

                                      

 

解释一下,该算法先将目标模式串们插入集合中,然后从文本串逐一进行计算子串S[i:i+m-1]的哈希值,如果哈希值相等并且该串存在目标字符串们的集合中则匹配成功一个目标字符串。原理比较简单,下一步就是来实现这个算法。未完待续!!!

参考文献:

https://en.wikipedia.org/wiki/Rabin%E2%80%93Karp_algorithm

《The Introduction to Algorithms》

### Rabin-Karp Algorithm 介绍 Rabin-Karp算法是种用于多模式搜索的字符串查找算法,能够高效地在个文本串中找到多个模式串的位置。该方法通过哈希函数计算子串的数值表示来加速匹配过程[^1]。 ### 原理 此算法的核心在于利用滚动散列技术减少不必要的字符比较次数。具体来说,在遍历目标字符串的过程中动态更新当前窗口内子串对应的哈希值;当遇到新的字符时只需基于前次的结果快速调整即可得到新位置处子串的新哈希码而无需重新计算整个表达式的值。如果两个不同长度相等但内容各异的序列产生了相同的指纹,则称为发生了碰撞现象。为了降低这种概率并提高准确性,通常会选择合适的质数作为模底,并采用多项式形式构建散列表达式[^2]。 ### 实现 下面给出了种简单的Java版本实现方式: ```java import java.util.ArrayList; import java.util.List; public class RabinKarpAlgorithm { private final int d = 256; // 字符集大小 private final long q = 101; // 随机选取的大素数 public List<Integer> rabinKarp(String txt, String pat) { int M = pat.length(); int N = txt.length(); long h = power(d, M - 1); long p = 0, t = 0; ArrayList<Integer> result = new ArrayList<>(); for (int i = 0; i < M; ++i){ p = (d * p + pat.charAt(i)) % q; t = (d * t + txt.charAt(i)) % q; } if(p == t && check(txt, pat, 0)) result.add(0); for(int s = 1 ;s<=N-M;++s){ t = (t - txt.charAt(s-1)*h)%q; while(t<0)t+=q; t=(t*d+txt.charAt(s+M-1))%q; if(p==t&&check(txt,pat,s)){ result.add(s); } } return result; } boolean check(String T,String P,int index){ for(int i=0;i<P.length();++i) if(T.charAt(index+i)!=P.charAt(i)) return false; return true; } } ``` 这段代码定义了个`rabinKarp()` 方法用来接收待查母串 `txt` 和模式串 `pat`, 并返回所有匹配起始下标的集合。这里还实现了辅助性的幂运算函数 `power()`. 此外,考虑到可能存在哈希冲突的情况,因此每次判定之前都会调用 `check()` 函数做最终验证以确保结果无误[^3]. ### 应用 由于其高效的预处理时间和较低的空间复杂度特性,使得Rabin-Karp非常适合应用于大规模数据环境下的批量查询场景之中,比如文件系统中的重复检测、基因组分析领域内的相似片段定位等问题求解上均有着广泛的应用前景。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值