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 ...
随机推荐
- C标准头文件<errno.h>
声明了错误处理相关的宏 errno errno即error number,在程序启动时被设为0,当某个库函数运行出现错误的时候,会将相应的能表达错误类型的数字赋值给这个左值,这些数字往往有相应的宏来表 ...
- 开源物联网框架ServerSuperIO 3.0正式发布(C#),跨平台:Win&Win10 Iot&Ubuntu&Ubuntu Mate,一套设备驱动跨平台挂载,附:开发套件和教程。
3.0版本主要更新内容: 1.增加跨平台能力:Win&Win10 Iot&Ubuntu&Ubuntu Mate 2.统一设备驱动接口:可以一套设备驱动,跨平台挂载运行,降低人力 ...
- Easyui dialog中嵌入iframe
如果easyui dialog的地址属性用href超链接,easyui 不会加载整个url页面,只会截取url目标页的body体间的html, 如果想加载把其他页面 加载进dialog的iframe中 ...
- 十种MYSQL显错注入原理讲解(一)
开篇我要说下,在<代码审计:企业级Web代码安全架构>这本书中讲十种MYSQL显错注入,讲的很清楚. 感兴趣请去读完,若处于某种原因没读还想了解,那请继续往下. 1.count,rand, ...
- SYN Flood测试
由于工作需要对公司进行SYN Flood测试,在网上查了些资料,Youtube上找到最多的方法就是hping3工具来实现, 该工具已经预装在Kali下,具体操作用一条命令即可实现. hping3 -S ...
- iOS - ViewController的生命周期
iOS SDK中提供很多原生的ViewController,大大提高了我们的开发效率:那么下面我们就根据开发中我们常用的ViewController谈一谈它的生命周期: (一)按照结构和用法可以对iO ...
- iOS 应用程序生命周期
开发应用程序都要了解其生命周期. 今天我们接触一下iOS应用程序的生命周期, iOS的入口在main.m文件: int main(int argc, char * argv[]) { @autorel ...
- H5 表格的结构
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...
- ALM损坏后的恢复步骤
ALM是HP出品的软件开发生命周期软件,其全称是Application Lifecycle Management,其采用B/S结构,从需求,业务模型到测试用例和缺陷管理亦应具有,满足了一般软件企业对开 ...
- linux基本知识
1.默认不写端口号就是80端口 127.0.0.1.localhost都代表本机 2.linux下的用户管理: id:可以查看当前用户whoami:查看当前的用户who:看当前已经登录的用户w:也 ...