首先计算模式字符串的散列函数, 如果找到一个和模式字符串散列值相同的子字符串, 那么继续验证两者是否匹配.

这个过程等价于将模式保存在一个散列表中, 然后在文本中的所有子字符串查找. 但不需要为散列表预留任何空间, 因为它只有一个元素.

基本思想

长度为M的字符串对应着一个R进制的M位数, 为了用一张大小为Q的散列表来保存这种类型的键, 需要一个能够将R进制的M位数转化为一个0到Q-1之间的int值散列函数, 这里可以用除留取余法.

举个例子, 需要在文本 3 1 4 1 5 9 2 6 5 3 5 8 9 7 9 3 查找模式 2 6 5 3 5, 这里R=10, 取Q=997, 则散列值为

2 6 5 3 6 % 997 = 613

然后计算文本中所有长度为5的子字符串并寻找匹配

3 1 4 1 5 % 997 = 508

1 4 1 5 9 % 997 = 201

......

2 6 5 3 6 % 997 = 613 (匹配)

计算散列函数

对于5位的数值, 只需要使用int就可以完成所有需要的计算, 但是当模式长度太大时, 我们使用Horner方法计算模式字符串的散列值

2 % 997 = 2

2 6 % 997 = (2*10 + 6) % 997 = 26

2 6 5 % 997 = (26*10 + 5) % 997 = 265

2 6 5 3 % 997 = (265*10 + 3) % 997 = 659

2 6 5 3 5 % 997 = (659*10 + 5) % 997 = 613

这里关键的一点就是在于不需要保存这些数的值, 只需保存它们除以Q之后的余数.

取余操作的一个基本性质是如果每次算术操作之后都将结果除以Q并取余, 这等价于在完成所有算术操作之后再将最后的结果除以Q并取余.

算法实现

3 1 4 1 5 9 2 6 5 3 5 8 9 7 9 3

3 % 997 = 3

3 1 % 997 = (3*10 + 1) %997 = 31

3 1 4 % 997 = (31*10 + 4) % 997 = 314

3 1 4 1 % 997 = (314*10 + 1) % 997 = 150

3 1 4 1 5 % 997 = (150*10 + 5) % 997 = 508

1 4 1 5 9 % 997 = ( (508 + 3*(997 - 30) ) *10 + 9) % 997 = 201

4 1 5 9 2 % 997 = ( (201 + 1*(997 - 30) ) *10 + 2) % 997 = 715

    ......

             2 6 5 3 6 % 997 =  ( (929 + 9*(997 - 30) ) *10 + 5) % 997 = 613

构造函数为模式字符串计算了散列值patHash并在变量中保存了R^(M-1) mod Q的值, hashSearch()计算了文本前M个字母的散列值并和模式字符串的散列值比较, 如果没有匹配, 文本指针继续下移一位, 计算新的散列值再次比较,知道成功或结束.

import java.math.BigInteger;
import java.util.Random; import edu.princeton.cs.algs4.StdOut; public class RabinKarp {
private String pat; //模式字符串
private long patHash; //模式字符串散列值
private int M; //模式字符串的长度
private long Q; //很大的素数
private int R; //字母表的大小
private long RM; //R^(M-1) % Q public RabinKarp(char[] pat, int R){
this.pat = String.valueOf(pat);
this.R = R;
} public RabinKarp(String pat){
this.pat = pat;
R = 256;
M = pat.length();
Q = longRandomPrime(); RM = 1;
for(int i=1; i<=M-1; i++){
RM = (R * RM) % Q;
}
patHash = hash(pat, M);
} private long hash(String str, int M){
long h = 0;
for(int i=0; i < M; i++){
h = (R * h + str.charAt(i)) % Q;
}
return h;
} public boolean check(String txt,int i){
for(int j = 0; j < M; j++){
if(pat.charAt(j) != txt.charAt(i+j))
return false;
}
return true;
} private static long longRandomPrime() {
BigInteger prime = BigInteger.probablePrime(31, new Random());
return prime.longValue();
} private int search(String txt){
int N = txt.length();
if(N < M) return N;
long txtHash = hash(txt,M); if((txtHash == patHash) && check(txt, 0)) return 0;
for(int i = M; i < N; i++){
txtHash = (txtHash + Q - RM*txt.charAt(i-M) % Q) % Q;
txtHash = (txtHash*R + txt.charAt(i)) % Q;
int offset = i-M+1;
if((patHash == txtHash) && check(txt, offset))
return offset;
}
return N;
} public static void main(String[] args) {
String pat = args[0];
String txt = args[1]; RabinKarp searcher = new RabinKarp(pat);
int offset = searcher.search(txt);
// print results
StdOut.println("text: " + txt); // from brute force search method 1
StdOut.print("pattern: ");
for (int i = 0; i < offset; i++)
StdOut.print(" ");
StdOut.println(pat);
}
}

上面代码中的求模运算的方法可以参考初数论里面的同模定理.

Rabin-Karp指纹字符串查找算法的更多相关文章

  1. Rabin-Karp字符串查找算法

    1.简介 暴力字符串匹配(brute force string matching)是子串匹配算法中最基本的一种,它确实有自己的优点,比如它并不需要对文本(text)或模式串(pattern)进行预处理 ...

  2. KMP 算法 & 字符串查找算法

    KMP算法 Knuth–Morris–Pratt algorithm 克努斯-莫里斯-普拉特 算法 algorithm kmp_search: input: an array of character ...

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

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

  4. 字符串查找算法总结(暴力匹配、KMP 算法、Boyer-Moore 算法和 Sunday 算法)

    字符串匹配是字符串的一种基本操作:给定一个长度为 M 的文本和一个长度为 N 的模式串,在文本中找到一个和该模式相符的子字符串,并返回该字字符串在文本中的位置. KMP 算法,全称是 Knuth-Mo ...

  5. KMP字符串查找算法

    #include <iostream> #include <windows.h> using namespace std; void get_next(char *str,in ...

  6. 数据结构与算法--Boyer-Moore和Rabin-Karp子字符串查找

    数据结构与算法--Boyer-Moore和Rabin-Karp子字符串查找 Boyer-Moore字符串查找算法 注意,<算法4>上将这个版本的实现称为Broyer-Moore算法,我看了 ...

  7. Sunday算法(字符串查找、匹配)

    字符串查找算法中,最著名的两个是KMP算法(Knuth-Morris-Pratt)和BM算法(Boyer-Moore).两个算法在最坏情况下均具有线性的查找时间.但是在实用上,KMP算法并不比最简单的 ...

  8. 字符串类——KMP子串查找算法

    1, 如何在目标字符串 s 中,查找是否存在子串 p(本文代码已集成到字符串类——字符串类的创建(上)中,这里讲述KMP实现原理) ? 1,朴素算法: 2,朴素解法的问题: 1,问题:有时候右移一位是 ...

  9. 字符串查找String.IndexOf

    String.indexOf的模拟实现,没想象中有多么高深的查找算法,就是最普通的遍历查找 思路:先找到第一个相同的字符,然后依次比较后面的字符,若都相等则表示查找成功 /** * 查找字符串patt ...

随机推荐

  1. Android开机动画

    Android系统的开机动画可分为三个部分,kernel启动,init进程启动,android系统服务启动.这三个开机动画都是在一个叫做 帧缓冲区(frame buffer)的硬件设备上进行渲染绘制的 ...

  2. Yii 2.x Behavior - 类图

    yii\base\Component  继承这个类的类都具备扩展行为的能力

  3. 搜狗输入法linux安装 以及 12个依赖包下载链接分享

    搜狗输入法linux安装版,先安装各种依赖包,大概12个依赖,可能中途还需要其他依赖,可以效仿解决依赖问题.如图这12个文件要是手动点击下载,那也太笨点了,我们要用shell命令批量下载.命令如下:w ...

  4. jQuery获取短信验证码+倒计时实现

    jQuery 短信验证码倒计时 <script type="text/javascript" charset="utf-8"> $(function ...

  5. Windows 批处理设置dns ,解决能上qq不能开网页

    对于windows 7 @echo off netsh interface ip set dns "本地连接" static 114.114.114.114 primary net ...

  6. Allocators与Criterion的相同点及区别

    C++98: 1.相同点: Allocators having the same type were assumed to be equal so that memory allocated by o ...

  7. oracle容器化docker解决方案

    Docker提供了轻量级的虚拟化,它几乎没有任何额外开销. 提供了一个从开发到上线均一致的环境. 开发效率:一是我们想让开发环境尽量贴近生产环境 二是我们想快速搭建开发环境   基于docker研发小 ...

  8. GridView 树形结构分组的功能

    在“会飞的鱼”博客中看到GridView实现树形结构的代码,经过修改,添加了树形结构中的复选框功能,欢迎吐槽. 源地址:http://www.cnblogs.com/chhuic/archive/20 ...

  9. Spark作业调度阶段分析

    Spark作为分布式的大数据处理框架必然或涉及到大量的作业调度,如果能够理解Spark中的调度对我们编写或优化Spark程序都是有很大帮助的: 在Spark中存在转换操作(Transformation ...

  10. mysql的缓冲查询和非缓冲查询

    最近在开发一个PHP程序时遇到了下面的错误: PHP Fatal error: Allowed memory size of 268 435 456 bytes exhausted 错误信息显示允许的 ...