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)的硬件设备上进行渲染绘制的 ...
- 航旅事业群面试(li)
一.JVM 1.如何观察垃圾回收? 2.JAVA应用的JVM参数调优? 3.举例说明你所知道的JVM profile方法? 4.GC tunning实战.假如你是一个大型网站的总架构师,有次web应用 ...
- JAVA调用R
JAVA很适合开发应用系统,但是数学建模和计算能力非其所长,如果该系统需要进行大量的统计或者优化的计算,调用R是一种很好的方式.JAVA负责系统的构建,R用来做运算引擎,从而实现应用型和分析性相结合的 ...
- 理解RESTful架构
越来越多的人开始意识到,网站即软件,而且是一种新型的软件. 这种"互联网软件"采用客户端/服务器模式,建立在分布式体系上,通过互联网通信,具有高延时(high latency).高 ...
- ERDAS 2014安装破解及汉化图文教程
关于ERDAS 2014 的安装破解及其汉化过程: 说明:汉化只是之针对英语能力不能正常满足使用软件的朋友们.如果英语过专八,完全没有必要汉化!在此之前查了好多关于ERDAS汉化的文档,可惜只是简单的 ...
- SharePoint 2013 沙盒解决方案不能激活(激活按钮不可用)
把沙盒解决方案上传到目标站点的"解决方案"库中,发现"激活"按钮是灰掉的,不可用. 首先,我想到的是权限不足,所以 "以管理员身份"启动IE ...
- Andriod 自定义控件之音频条
今天我们实现一个直接继承于View的全新控件.大家都知道音乐播放器吧,在点击一首歌进行播放时,通常会有一块区域用于显示音频条,我们今天就来学习下,播放器音频条的实现. 首先我们还是先定义一个类,直接继 ...
- 读<<领域驱动设计-软件核心复杂性应对之道>>有感
道可道,非常道. 名可名,非常名. 无名天地之始,有名万物之母. ---老子 关于标题 好久没写东西了,动笔的动机是看完了一本书,想写点总结性的东西,一是为了回顾一下梳理知识点,二是为了日后遗忘时能有 ...
- 转载文章——Hadoop学习
转载地址:http://www.iteye.com/blogs/subjects/zy19982004?page=2 一.Hadoop社区版和发行版 社区版:我们把Apache社区一直开发的Hadoo ...
- ORACLE分区表梳理系列(一)- 分区表概述、分类、使用方法及注意事项
版权声明:本文发布于http://www.cnblogs.com/yumiko/,版权由Yumiko_sunny所有,欢迎转载.转载时,请在文章明显位置注明原文链接.若在未经作者同意的情况下,将本文内 ...