算法分析:预处理时间Θ(m),即求h,p,t的时间为,匹配时间在最坏情况下为Θ((n-m-1)m),因为可能出现每次都是可能命中点的情况。如T=a^n,P=a^m,此种情况下验证时间为Θ((n-m-1)m)。当然实际中,可能的命中点一般很少。假设有c个,则算法的期望匹配时间为O(n-m+1 +cm)=O(m+n),当m<<n时,期望匹配时间为O(n).

Rabin-karp算法是朴素字符串匹配算法的一个特例。当字母表∑为d进制数时,即∑={0,1,2,…d-1}。如当d=10时字母表中的每个字符都是一个十进制数。我们在比较两个长度为m的子串时,可以把这两个子串当作整数进行比较,而不用逐个字符比较,从而在某种程度上减少算法时间。

把一个由d进制数字组成的字符串转换成相应的十进制整数,这是大家曾经都写过的东西,一个可能的简单实现如下:

int ConvertToInt(const char* str, int d)
{
int ans =;
int m = strlen(str);
for(int i=; i<m; i++)
{
ans = ans*d + str[i]-'';
}
return ans;
}

这就是所谓的霍纳法则(Horner’s rule)

对一个长度为m的子串,求其对应的整数值,其复杂度为O(m),如果不做一些特殊的处理,每次把子串和模式P比较时,先求子串对应的整数t,再与模式P对应的整数值p比较,算法的复杂度并没有得到改进。如果用t(s)表示当前位移下子串T[s+1..s+m]的值,我们注意到把模式向右滑动一个窗口之后,下一个m位的子串T[s+2..s+m+1]对应的整数值t(s+1)和t(s)相比,只是去掉了最高位数字T[s+1],增加了一个最低位的数字T[s+m+1]。因而t(s+1)和t(s)之间有如下关系式:

t(s+1)=d*(t(s)-T[s+1]*d^(m-1))+T[s+m+1]

例如对于文本十进制数字组成的文本”2359023141526739921”,d=10,m=5,当s=6时,t(s)=”31415”,T[s+1]=T[7]=3,T[s+m+1]=T[12]=2,所以

t(s+1) =10*(31415-3*10^4)+2=14152

因而我们初始时,只用求p=P[1..m],t0=T[1..m],t(s+1)的求值可以在每次循环迭代中去求,避免了多次函数调用的开销。因而一个可能的实现如下:

void Bad_Rabin_Karp_Matcher(const char* T, const char* P, int d)
{
int n = strlen(T);
int m = strlen(P);
int h = pow(static_cast<double>(d),m-);//h=d^(m-1)
int p=;
int t=; for(int i=; i<m; i++)
{
p = p*d + P[i]-'';//p=P[0,m-1]
t = t*d + T[i]-'';//t=T[0,m-1]
}
for(int s=; s<=n-m; s++)
{
if(p==t)
{
cout<<"Pattern occurs with shift"<<s<<endl;
}
if(s<n-m)
{
t = d*(t-h*(T[s]-'')) + T[s+m]-'';
}
}
}

注:

(1) 在前面讲解过程中数组下标从1开始而在实际编程中数组下标是从0开始的,所以代码和描述的有点差异,但是原理是一样的。

(2)注意到我们求p,t,h的过程,不管用的是int还是long long型,当m增大时,肯定会产生溢出,比如说一个32位的int型表示的最大整数也就是20多亿。

(3)一般做过一些ACM题目的同学,知道在这种情况我们可以通过对一个数求模来防止溢出,记其为q

(4)这里又出现了一个问题,当p≠t (mod q)时,的确可以推出p≠q;但是当p=t (mod q)时,推不出p=t。即可能出现所谓的伪命中点,即p=t (mod q)但是p≠q。所以当p=t (mod q)时,我们要进行额外检查,依次比较这m个字符,看其是否真的命中。

(5)通过把q设置为一个较大的素数,可以有效的减少伪命中点数,因而可以减小额外检查的开销。

通过以上分析,改进后一个可能的算法实现如下:

void Rabin_Karp_Matcher(const char* T, const char* P, int d, int q)
{
int n = strlen(T);
int m = strlen(P);
int p = ;
int t = ;
int h = d;//当m=1时有问题,此处应该为h=1,下面循环中k初始为1
for(int k=; k<m; k++)
{
h = h*d % q;
}
for(int i=; i<m; i++)
{
p = (p*d + P[i]-'') % q;
t = (t*d + T[i]-'') % q;
} for(int s=; s<=n-m; s++)
{
// cout<<"p="<<p<<" t="<<t<<endl;
if(p==t)
{
if(strncmp(T+s,P,m)==)
{
cout<<"Pattern occurs with shift"<<s<<endl;
}
}
if(s<n-m)
{
t = (d*(t-h*(T[s]-''))+T[s+m]-'') % q;//此处也有问题,t可能小于0
}
}
}

但是运行结果和和预期不符,比如说你输入P=”111111”,T=”1”,结果只输出了一个位移值0,输入T=”1234”,P=”34”居然没有有效的位移输出。

分析代码,唯一可能的原因就是求余过程出现到了问题,因此我们在循环中比较p和t的值之前先输出它们的值,便于分析。果不其然,拿P=”111111”,T=”1”的输出作为示例,除了第一个位移p和t相等,后面的位移t居然是负值。也就是说对q求余的结果可能出现[-q+1,-1]范围的负值,为了计算的正确性,我们要保证迭代的过程中t都为正值。当t<0时,只需加上q即可。当然初始计算p和t的值时,p和t是不可能为负值的。修改完代码后,上面举的第二个输入用例运行正确,但是第一个仍然有问题,分析发现,原来当模式P只含一个字符时m=1,h=d^(m-1)=d^0=1,而前面我在求h时,只想到h是m-1个的相乘,把h初始化为了d,疏忽了m可能取1,所以出现了此种情况。到此分析结束,完整正确的函数及测试代码如下:

字符串hash-RK算法讲解二的更多相关文章

  1. 独树一帜的字符串匹配算法——RK算法

    参加了雅虎2015校招,笔试成绩还不错,谁知初面第一题就被问了个字符串匹配,要求不能使用KMP,但要和KMP一样优,当时瞬间就呵呵了.后经过面试官的一再提示,也还是没有成功在面试现场写得.现将该算法记 ...

  2. 【字符串算法1】 再谈字符串Hash(优雅的暴力)

    [字符串算法1] 字符串Hash(优雅的暴力) [字符串算法2]Manacher算法 [字符串算法3]KMP算法 这里将讲述  [字符串算法1] 字符串Hash 老版原文: RK哈希(Rabin_Ka ...

  3. 如何在文本编辑器中实现搜索功能? 字符串比较算法 BF算法 RK算法

    1.暴力比较 BF算法 2.比较字串hash值 RK算法 //字符串匹配 public class StringCmp { //约定:A主串长 n ,B模式串 长m.要求:在A串中找到B串匹配的下标 ...

  4. 记录几个经典的字符串hash算法

    记录几个经典的字符串hash算法,方便以后查看: 推荐一篇文章: http://www.partow.net/programming/hashfunctions/# (1)暴雪字符串hash #inc ...

  5. KMP替代算法——字符串Hash

    很久以前写的... 今天来谈谈一种用来替代KMP算法的奇葩算法--字符串Hash 例题:给你两个字符串p和s,求出p在s中出现的次数.(字符串长度小于等于1000000) 字符串的Hash 根据字面意 ...

  6. 字符串查找算法的改进-hash查找算法

    字符串查找即为特征查找: 特征即位hash: 1.将待查找的字符串hash: 2.在容器字符串中找头字符匹配的字符串,并进行hash: 3.比较hash的结果:相同即位匹配: hash算法的设计为其中 ...

  7. URAL - 1486 二维字符串HASH

    题目链接:http://acm.timus.ru/problem.aspx?space=1&num=1486 题意:给定一个n*m的字符矩阵,问你是否存在两个不重合(可以有交集)的正方形矩阵完 ...

  8. 字符串Hash算法比较

    基本概念所谓完美哈希函数,就是指没有冲突的哈希函数,即对任意的 key1 != key2 有h(key1) != h(key2).设定义域为X,值域为Y, n=|X|,m=|Y|,那么肯定有m> ...

  9. (通俗易懂小白入门)字符串Hash+map判重——暴力且优雅

    字符串Hash 今天我们要讲解的是用于处理字符串匹配查重的一个算法,当我们处理一些问题如给出10000个字符串输出其中不同的个数,或者给一个长度100000的字符串,找出其中相同的字符串有多少个(这样 ...

随机推荐

  1. 东方14ACM小组 Challenge 11

    总时间限制:  10000ms 单个测试点时间限制:  1000ms 内存限制:  262144kB 描述 给一个长为N的数列,有M次操作,每次操作是以下两种之一: (1)修改数列中的一个数 (2)求 ...

  2. vijos 1081 野生动物园 函数式线段树

    描述 cjBBteam拥有一个很大的野生动物园.这个动物园坐落在一个狭长的山谷内,这个区域从南到北被划分成N个区域,每个区域都饲养着一头狮子.这些狮子从北到南编号为1,2,3,…,N.每头狮子都有一个 ...

  3. LightOJ 1028 - Trailing Zeroes (I) 质因数分解/排列组合

    题意:10000组数据 问一个数n[1,1e12] 在k进制下有末尾0的k的个数. 思路:题意很明显,就是求n的因子个数,本来想直接预处理欧拉函数,然后拿它减n就行了.但注意是1e12次方法不可行.而 ...

  4. yum快速安装gitlab

    安装gitlab前戏使用官方的源,还是比较慢的,gitlab官方提供了一个清华大学的源 新建 /etc/yum.repos.d/gitlab-ce.repo,内容为 源[gitlab-ce]name= ...

  5. javascript 访问cookie信息

    在Javascript脚本里,一个cookie 实际就是一个字符串属性.当你读取cookie的值时,就得到一个字符串,里面当前WEB页使用的所有cookies的名称和值.每个cookie除了 name ...

  6. 【BZOJ1449&&2895】球队预算 [费用流]

    球队预算 Time Limit: 10 Sec  Memory Limit: 256 MB[Submit][Status][Discuss] Description 在一个篮球联赛里,有n支球队, 球 ...

  7. bzoj 1776: [Usaco2010 Hol]cowpol 奶牛政坛——树的直径

    农夫约翰的奶牛住在N (2 <= N <= 200,000)片不同的草地上,标号为1到N.恰好有N-1条单位长度的双向道路,用各种各样的方法连接这些草地.而且从每片草地出发都可以抵达其他所 ...

  8. python学习笔记(一)之为什么学习python

    python的特点: 跨平台 实现同一个功能是Java代码的1/5 python应用范围: 操作系统 web 3D动画 企业应用 云计算 如何学习python? 学习语法 验证例子 学会总结 课外实践

  9. Sketch VS Photoshop

    参考:http://mp.weixin.qq.com/s?__biz=MjM5NTQ5MjIyMA==&mid=217309554&idx=4&sn=4d6a5239ca813 ...

  10. js_layer弹窗的使用和总结

    2018-04-10 一张呈现给用户的网页,会有很多种交互,比如连不上网络,用户点击按钮时向后台请求数据不成功等等.像这些情况,用户是看不见的, 要给用户更好的体验,在特定的时间,给客户反馈内容.实时 ...