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

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

基本思想

长度为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. Navisworks 2014 Api 简单的使用

    初次接触Navisworks Api  .NET 的二次开发.主要是研究了一下.关于NavisWorks 结构树的加载. void LoadModel() { //清空当前的结构树信息 treeVie ...

  2. openresty 前端开发入门三之JSON篇

    这章主要介绍一下,lua怎么返回一个json字符串,怎么把一个table转成json字符串,又怎么把一个json字符串转成json 其实很简答,直接使用cjson库的encode.decode方法即可 ...

  3. perl 如何匹配ASCII码以及ASCII码转换

    匹配ASCII码:   /[:ascii:]/ ASCII码转换为数字: ord() 数字转换为ASCII码: chr()

  4. WEB项目会话集群的三种办法

    web集群时session同步的3种方法 在做了web集群后,你肯定会首先考虑session同步问题,因为通过负载均衡后,同一个IP访问同一个页面会被分配到不同的服务器上, 如果session不同步的 ...

  5. .Net 搭建 RESTful

    1.新建项目 ---> 选择 web 应用程序 选择 webApi 2. 创建一个httpmodeule类 放到app_data文件夹下 public class MyHttpModule : ...

  6. [deviceone开发]-do_Album的简单示例

    一.简介do_Album用来打开手机系统提供的相册,能选择一张或多张图片返回给开发者,通常相册的图片比较大,要经过缩放.有的时候用户也需要把别的地方获取到到图片收藏到系统相册.这个示例简单展示这个组件 ...

  7. JavaScript执行环境

    执行环境(Execution Context,也称为"执行上下文")是JavaScript中最为重要的一个概念.执行环境定义了变量或函数有权访问的其它数据,决定了各自的行为.当Ja ...

  8. 使用WebRTC搭建前端视频聊天室——入门篇

    http://segmentfault.com/a/1190000000436544 什么是WebRTC? 众所周知,浏览器本身不支持相互之间直接建立信道进行通信,都是通过服务器进行中转.比如现在有两 ...

  9. js与native交互

    js与native交互 UIWebView Native调用JS,使用stringByEvaluatingJavaScriptFromString来解释执行js脚本. //script即为要执行的js ...

  10. android Service介绍

    一.简介 android中service(服务)运行于后台,没有界面.和其他组件一样,service也运行在主线程中,因此不能用它来做耗时的请求或者动作.可以在服务中开启线程,在线程中做耗时操作.可以 ...