前几天用到了golang strings的LastIndex(s, substrstring) int ,Index(s, substrstring)int 这两个函数就跳进去看了看发现用了Rabin-Karp 这个算法,之前没有听说过,不久前我们leader还问了kmp 算法,kmp 算法当学生的时候写过,就想把两个算法搞在一起写一下,总结一下
Rabin-Karp 算法
其实原理很简单就是把字符串的变成数字,原来字符串的逐字比较变成了比较两个数字的大小,显然比较数字会快很多,关键是怎样比较数字
golang 的 Rabin-Karp 算法
primeRK 是一个很大的素数,为什么选择这个数我还没有查到,似乎很多(hash)算法用到这个数字,子串转化为
相当于将字符转化为primeRK进制的一个个数(primeRK很大,一般的字符集都可以容纳吧), 一个字符串的转化为primeRK进制数相互比较(数值太大可以比较模,hashStr返回的是uint32位相当于一种取模,超过32位会被截断),如果数值不相等,去掉最左边的字符相当于去掉左边的最高位,原数值扩大原来的primeRK,继续搜寻加上右边的字符,相当于加上一个最低位, 如果两个数值相等不能简单的判定两个字符串相等,需要再做一次比较
对于LastIndex Index 这两个函数来说也做了些优化处理,比如子串和原字符串都比较小的情况下就直接走汇编函数比较(不太懂汇编),在子串比较小而原字符串不小的情况下先寻找子串首字符位置,找到后比较字符
kmp 算法
这个网上有很多文章我感觉写的比较好的还是july,我自己念书的时候感觉数据结构里面写的还是很简单的,但是我家那本《算法》(名字就叫算法)就很难懂,kmp算法的主要思想就是一个模式字符串可能包含这个字符串的前缀,比如说一个模式字符串 fbbfbabbd 和 fbbfbbaisjdfkbabb进行匹配当比较到模式字符安串的第5个字符的时候不匹配,按照暴力搜索,应该在匹配字符串的第二位开始直接比较,但是我们知道两个字符串的前五位已经相等了,那么只需要从匹配字符串的第三位开始比较久可以了
关键就是要找到模式字符串的规律 ,在字符串中找到能够和字符串前缀类似的规律,找到这种规律的方法有很多 ,算法那本书用的是个二位数组,表示这个字符在摸个位置移动的位数,一般就是求得一个移动字符串的数组,这个july的博客写的就很详细
从头到尾彻底理解KMP(2014年8月22日版) - CSDN博客
算法:
golang 版 :
BM算法
bm算法诞生的比kmp算法早,而且速度也比较快,bm算法其实和kmp算法差不太多,都是先对模式进行预处理,只不过bm是从模式字符串的后面往前匹配,在不匹配的时候好后缀和坏字符原则选取移动最大的距离,依次比较
好后缀原则就是在之前几个字符都匹配成功的情况下看下在还没有匹配的模式字符串中是否有和他相同后缀的子串,如果有的话就移动就和之前最近的好后缀对齐,没有移动距离就是整个模式字符串的长度
坏字符原则 坏字符就是在当前字符失配的情况下,看下这个字符在字符串中的最右位置,移动并对齐,如果最右位置在失配位置的右边,这种情况下不考虑(此时会有好后缀,移动好后缀算出的距离即可),如果模式字符串中没有这个字符就移动模式字符串的距离,重新开从后往前匹配
由于这个算法我写的有点长就不粘了= ̄ω ̄= (话说简书粘代码也很长)
总结:
总的来说bm 和 kmp 都算是暴力搜索的一种改进,kmp在模式字符串没有重复前缀应该效率不是很高吧,算法那本书说在实际应用中暴力搜索的效率没有很低,因为很多就在首字母过滤掉了,但是如果是二进制文件Rabin-Karp 算法的效率还是更高些,而且对二维数据支持更好
参考:
从头到尾彻底理解KMP(2014年8月22日版) - CSDN博客
Boyer-Moore 字符串匹配算法 - 匠心十年 - 博客园
《算法》java版