Rabin-Karp算法在go的实现

原文链接 github

简介

Rabin-Karp字符串快速查找算法和FNV hash算法是golang中strings包中字符串查所用到的具体算法,算法的核心就在于循环hash,而 FNV hash则是散列方法的具体算法实现。

算法思想

Rabin-Karp算法思想:

  • 假设待匹配字符串长度M,目标字符串长度N(N>M)
  • 首先计算待匹配字符串hash,计算目标字符串前M个字符hash
  • 比较前两个hash值,比较次数N-M+1
    • 若hash不相等,继续计算目标字符串下一个长度为M的hash并继续循环比较
    • 若hash相等则再次判断字符串是否相等已确保正确性

FNV hash:

将字符串看作是字符串长度的整数,这个数的进制是一个质数。计算出来结果之后,按照哈希的范围求余数得到结果。

其中不同机制对应质数分别是:

32 bit FNV_prime = 224 + 28 + 0x93 = 16777619

64 bit FNV_prime = 240 + 28 + 0xb3 = 1099511628211

128 bit FNV_prime = 288 + 28 + 0x3b = 309485009821345068724781371

256 bit FNV_prime = 2168 + 28 + 0x63 = 374144419156711147060143317175368453031918731002211

512 bit FNV_prime = 2344 + 28 + 0x57 =
35835915874844867368919076489095108449946327955754392558399825615420669938882575
126094039892345713852759

1024 bit FNV_prime = 2680 + 28 + 0x8d =
50164565101131186554345988110352789550307653454047907443030175238311120551081474
51509157692220295382716162651878526895249385292291816524375083746691371804094271
873160484737966720260389217684476157468082573

以上这几个数都是质数(哈希的理论基石,质数分辨定理),简单地说就是:n个不同的质数可以“分辨”的连续整数的个数和他们的乘积相等。“分辨”就是指这些连续的整数不可能有完全相同的余数序列。证明详见

如果想要得到不是上面进制的hash:

比如想得到24位的哈希值,方法:取上面比24大的最小的位数,当然是32了,先算对应32位哈希值,再转换成24位的。
转换方法:32 - 24 = 8, 好了把得到的32砍成两段,高8位最和低24位。第8位与低24位中的低8位做抑或,得到的24位值是最终结果。 (hash»24) ^ (hash & 0xFFFFFF);

如果想得到范围在0~9999的哈希值,方法:取上面比9999大的最小的位数,当然是32,先算对应32位哈希值,再mod(9999 +1)。

如上所述,结合Rabin-Karp的思想加上FNV hash就可以实现所谓的字符串快速查找算法了。

结合golang源码

src/strings/strings.go

// Rabin-Karp 中需要使用的32位FNV hash算法中的基础质数(相当于进制)
const  primeRK = 16777619

// hash散列方法, 返回字符串hash以及 primeRK的k-1(len(sep)-1)次方  
func hashStr(sep string) (uint32, uint32) {  
    hash := uint32(0)  
    for i := 0; i < len(sep); i++ {  
        hash = hash*primeRK + uint32(sep[i]) // 循环得到字符串hash  
    }  

    // 位运算巧妙的获取 primeRK 的 len(sqp)-1 次方  
    var pow, sq uint32 = 1, primeRK  
    for i := len(sep); i > 0; i >>= 1 {  
        if i&1 != 0 {  
            pow *= sq  
        }  
        sq *= sq  
    }
    return hash, pow  
}  

func indexRabinKarp(s, substr string) int {  
    // Rabin-Karp search  
    hashss, pow := hashStr(substr)  
    n := len(substr)  
    var h uint32  
    // 计算目标字符串前n位hash并与待匹配字符串hash进行对比  
    for i := 0; i < n; i++ {  
        h = h*primeRK + uint32(s[i])  
    }  
    // hash相同并且字符串相等则返回当前位置下标  
    if h == hashss && s[:n] == substr {  
        return 0  
    }  

    // Rabin-Karp 算法的精华所在,相面详细介绍  
    for i := n; i < len(s); {  
        h *= primeRK  
        h += uint32(s[i])  
        h -= pow * uint32(s[i-n])  
        i++  
        if h == hashss && s[i-n:i] == substr {  
            return i - n  
        }  
    }  
    return -1  
}

结合源码可以知道:如果现在我们要求第i位往后k个长度字符串的hash可以列个公式

其中:s[i] 表示第i位字节对应32位整数也就是上面uint32(s[i]) (这里强转一下也就是对2^32次方取余了),R 就是 对应进制的FNV_prime

由上述类推H(i+1)的hash公式就是:

由此可以看出来:每次我们其实不用重新计算整个字符串的hash而是直接原hash值乘以R加上s[k-1]并且减去s[i]R^(k-1),这里也就是 FNV_prime的k-1次方,对应上面代码:

var pow, sq uint32 = 1, primeRK  
for i := len(sep); i > 0; i >>= 1 {
    if i&1 != 0 {
        pow *= sq  
    }  
    sq *= sq
}

相对于暴力匹配O(mn)是时间复杂度, Rabin-Karp 的时间复杂度在O(m+n), 最坏的情况每次hash相同字符串不相同时间复杂度会变成O(mn)但是这种情况比较罕见。

Rabin-Karp 还有个优点在于他可以进行多模式匹配,比如论文重复性检测,只要预热计算出所有带匹配字符串的hash,目标字符串的遍历比较时只是多一步比较所有待匹配字符串hash。如果待匹配字符串个数是k,那么 Rabin-Karp 的时间复杂度是O(nk)。

原文链接 github

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,525评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,203评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,862评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,728评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,743评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,590评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,330评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,244评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,693评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,885评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,001评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,723评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,343评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,919评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,042评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,191评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,955评论 2 355