算法分析:预处理时间Θ(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. linux内存相关好文(转)

    话说团队的兄弟有一天问我,为啥咱唯一的一个服务器,内存都用完了,我还想在上面测性能呢.我一听,第一反应:不可能!我说你胡扯呢吧,咱那可是16G的一个物理机,上面就跑了git服务器,怎么可能把内存吃完了 ...

  2. Jmeter-7-在命令行中运行Jmeter.

    jmeter -n -t D:\Jmeter_result\Script_baidu.jmx -l D:\Jmeter_result\Script_baidu.txt jmeter -n -t D:\ ...

  3. Java连接Oracle数据库的三种连接方式

    背景: 这两天在学习Oracle数据库,这里就总结下自己上课所学的知识,同时记录下来,方便整理当天所学下的知识,也同时方便日后自己查询. SQL语句的话,这里我就不多讲了,感觉和其他的数据库(MySQ ...

  4. python初步学习-python数据类型之number(数值)

    数据类型之 Number python number 数据类型用于存储数值. 数据类型是不容许改变的. 这就意味着如果改变 number 数据类型的值,将重新分配内存空间. python支持四种不同数 ...

  5. React的单向数据流与组件间的沟通

    今天来给大家总结下React的单向数据流与组件间的沟通. 首先,我认为使用React的最大好处在于:功能组件化,遵守前端可维护的原则. 先介绍单向数据流吧. React单向数据流: React是单向数 ...

  6. fundamentals of the jQuery library

    1.why is jquery Only 32kB minified and gzipped. Can also be included as an AMD module Supports CSS3 ...

  7. Python面向对象学习2(面向对象的语法和特性,待更新)

    上一个内容我们介绍了面向对象和面向对象场景现在我们来学习下语法和特性 1,面向对象基本语法: # -*- coding:utf-8 -*- # Author: Colin Yao class Dog( ...

  8. Java面向对象的三个特征与含义

    封装 1.英文为 encapsulation,实现信息隐藏: 2.把同一类事物的特性归纳到一个类中(属性和行为),隐藏对象的内部实现: 继承 1.英文为 inheritance: 2.继承的过程,是从 ...

  9. 在linux程序里面,知道一个函数地址,改函数是属于某个动态库的,怎么样得到这个动态库的全【转】

    转自:http://www.360doc.com/content/17/1012/11/48326749_694292472.shtml 另外dl_iterate_phdr可以查到当前进程所装在的所有 ...

  10. Exploring Qualcomm's TrustZone Implementation

    转自  http://bits-please.blogspot.com/2015/08   (需要FQ, 狗日的墙) In this blog post, we'll be exploring Qua ...