Rabin-Karp指纹字符串查找算法
首先计算模式字符串的散列函数, 如果找到一个和模式字符串散列值相同的子字符串, 那么继续验证两者是否匹配.
这个过程等价于将模式保存在一个散列表中, 然后在文本中的所有子字符串查找. 但不需要为散列表预留任何空间, 因为它只有一个元素.
基本思想
长度为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指纹字符串查找算法的更多相关文章
- Rabin-Karp字符串查找算法
1.简介 暴力字符串匹配(brute force string matching)是子串匹配算法中最基本的一种,它确实有自己的优点,比如它并不需要对文本(text)或模式串(pattern)进行预处理 ...
- KMP 算法 & 字符串查找算法
KMP算法 Knuth–Morris–Pratt algorithm 克努斯-莫里斯-普拉特 算法 algorithm kmp_search: input: an array of character ...
- 字符串查找算法的改进-hash查找算法
字符串查找即为特征查找: 特征即位hash: 1.将待查找的字符串hash: 2.在容器字符串中找头字符匹配的字符串,并进行hash: 3.比较hash的结果:相同即位匹配: hash算法的设计为其中 ...
- 字符串查找算法总结(暴力匹配、KMP 算法、Boyer-Moore 算法和 Sunday 算法)
字符串匹配是字符串的一种基本操作:给定一个长度为 M 的文本和一个长度为 N 的模式串,在文本中找到一个和该模式相符的子字符串,并返回该字字符串在文本中的位置. KMP 算法,全称是 Knuth-Mo ...
- KMP字符串查找算法
#include <iostream> #include <windows.h> using namespace std; void get_next(char *str,in ...
- 数据结构与算法--Boyer-Moore和Rabin-Karp子字符串查找
数据结构与算法--Boyer-Moore和Rabin-Karp子字符串查找 Boyer-Moore字符串查找算法 注意,<算法4>上将这个版本的实现称为Broyer-Moore算法,我看了 ...
- Sunday算法(字符串查找、匹配)
字符串查找算法中,最著名的两个是KMP算法(Knuth-Morris-Pratt)和BM算法(Boyer-Moore).两个算法在最坏情况下均具有线性的查找时间.但是在实用上,KMP算法并不比最简单的 ...
- 字符串类——KMP子串查找算法
1, 如何在目标字符串 s 中,查找是否存在子串 p(本文代码已集成到字符串类——字符串类的创建(上)中,这里讲述KMP实现原理) ? 1,朴素算法: 2,朴素解法的问题: 1,问题:有时候右移一位是 ...
- 字符串查找String.IndexOf
String.indexOf的模拟实现,没想象中有多么高深的查找算法,就是最普通的遍历查找 思路:先找到第一个相同的字符,然后依次比较后面的字符,若都相等则表示查找成功 /** * 查找字符串patt ...
随机推荐
- Android开机动画
Android系统的开机动画可分为三个部分,kernel启动,init进程启动,android系统服务启动.这三个开机动画都是在一个叫做 帧缓冲区(frame buffer)的硬件设备上进行渲染绘制的 ...
- Yii 2.x Behavior - 类图
yii\base\Component 继承这个类的类都具备扩展行为的能力
- 搜狗输入法linux安装 以及 12个依赖包下载链接分享
搜狗输入法linux安装版,先安装各种依赖包,大概12个依赖,可能中途还需要其他依赖,可以效仿解决依赖问题.如图这12个文件要是手动点击下载,那也太笨点了,我们要用shell命令批量下载.命令如下:w ...
- jQuery获取短信验证码+倒计时实现
jQuery 短信验证码倒计时 <script type="text/javascript" charset="utf-8"> $(function ...
- Windows 批处理设置dns ,解决能上qq不能开网页
对于windows 7 @echo off netsh interface ip set dns "本地连接" static 114.114.114.114 primary net ...
- Allocators与Criterion的相同点及区别
C++98: 1.相同点: Allocators having the same type were assumed to be equal so that memory allocated by o ...
- oracle容器化docker解决方案
Docker提供了轻量级的虚拟化,它几乎没有任何额外开销. 提供了一个从开发到上线均一致的环境. 开发效率:一是我们想让开发环境尽量贴近生产环境 二是我们想快速搭建开发环境 基于docker研发小 ...
- GridView 树形结构分组的功能
在“会飞的鱼”博客中看到GridView实现树形结构的代码,经过修改,添加了树形结构中的复选框功能,欢迎吐槽. 源地址:http://www.cnblogs.com/chhuic/archive/20 ...
- Spark作业调度阶段分析
Spark作为分布式的大数据处理框架必然或涉及到大量的作业调度,如果能够理解Spark中的调度对我们编写或优化Spark程序都是有很大帮助的: 在Spark中存在转换操作(Transformation ...
- mysql的缓冲查询和非缓冲查询
最近在开发一个PHP程序时遇到了下面的错误: PHP Fatal error: Allowed memory size of 268 435 456 bytes exhausted 错误信息显示允许的 ...