Rabin-Karp字符串匹配算法

Rabin-Karp算法的原理

    在一个字符串中查找一个子串,如果使用BF算法一个个的去比较那么最差的情况时间复杂度将达到O(m*n)。那么有没有什么办法可以简化呢?假设一个字符串中的字符全是数字,查找的子串也是数字。例如’32153152125’中查找’3152’那么就有办法可以简化这个过程,不断的便历父串去计算N(i) - N(i-n)*10**n(N(i)是父串前i下标组成的数字,n为子串的长度,**是幂运算),直到32153152 - 32150000 就可以得出来我们想要匹配的结果,时间复杂度将会是O(m+n)。那对于字符元素不是数字的可以用这个方式吗?当然可以,系统对于字符都是通过编码表示,例如a-z字符集就可以看做26进制的数字,下面就用这个来举例。当然数字太大就会溢出,需要使用取模操作来避免这种情况取模运算规则

一边计算父串数值一边对比子串

一开始我的思路就是先算出子串的数值,再一边计算父串前i下标一边做比较:

//错误示范
var strStr = function(haystack, needle) {//rabin-karp算法
    if(needle === '') return 0;
    let m = haystack.length;
    let n = needle.length;
    let b = 31,p = 10007;//b相当于进制,p是用来取模的质数
    let childhash = 0;
    for(let i=0;i<n;i++){//计算子串的数值
       childhash = (childhash*b+(needle.charCodeAt(i)-96))%p;
    }
    let faHash = 0;
    let h = [];//需要保存父串第i-n个数值用于计算
    console.log(m,n,childhash);
    for(let i=0;i<m;i++){
        faHash = (faHash*b+(haystack.charCodeAt(i)-96))%p;
        h[i] = faHash;
        if(i===n-1 && faHash === childhash) return 0;
        else if(i>=n && ((faHash - (h[i-n]*b**n)%p+p)%p === childhash)){//计算N(i) - N(i-n)*(进制)**n是否等于子串数值
            return i-n+1;
        }
    }
    return -1;
}

这么做有问题吗,在字符串比较短的时候不会出问题,但是一旦遇到长的字符串导致h[i-n]*b**n计算溢出那么得到的结果就不准确了,所以我们还需要进行一步,就是计算出h[i-n]*b**n的正确结果,通过循环取模保证中间结果都正确而不是直接计算幂,得到错误的结果。下面是正确的的做法:

var strStr = function(haystack, needle) {//rabin-karp算法
    if(needle === '') return 0;
    let m = haystack.length;
    let n = needle.length;
    let b = 31,p = 10007;//b相当于进制,p是用来取模的质数
    let childhash = 0,muti = 1;
    for(let i=0;i<n;i++){//计算子串的数值
       childhash = (childhash*b+(needle.charCodeAt(i)-96))%p;//charCodeAt取字符编码(a为97)
       muti = muti*b%p;//保证b**n幂计算的准确性,解决上面所犯的错误
    }
    let faHash = 0;
    let h = [];//h需要保存父串第i-n个数值用于计算
    for(let i=0;i<m;i++){
        faHash = (faHash*b+(haystack.charCodeAt(i)-96))%p;
        h[i] = faHash;
        if(i===n-1 && faHash===childhash) return 0; 
        else if(((h[i] - h[i-n]*muti)%p+ p)%p === childhash){//计算N(i) - N(i-n)*(进制)**n是否等于子串数值
            return i-n+1;
        }
    }
    return -1;
}
console.log(strStr('a','a'));
console.log(strStr('hello','ll'));
console.log(strStr('gwewqerwrwq','rw'));
console.log(strStr("ababcaababcaabc","ababcaabc"));

点击提交,终于通过了。这个方法还有个缺点就是可能出现不同的字符串计算得到的数值相同但是不是我们所找的子串,所以还需要判断下,但是这个可能性比较小,取好b和p值就能尽可能减少这种冲突。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值