Rabin-Karp 算法

概念

用于在 一个字符串 中查找 另外一个字符串 出现的位置。

与暴力法不同,基本原理就是比较字符串的 哈希码 ( HashCode ) , 快速的确定子字符串是否等于被查找的字符串

比较哈希值采用的是滚动哈希法

  • 如何计算哈希值:

    如 : “abcde” 的哈希码值为
\[a×31^4+b×31^3+c×31^2+d×31^1+e×31^0
\]
  • 滚动哈希法:

    母串是"abcde",子串是"cde"

    则母串先计算"abc"的哈希值:

    \[a×31^2+b×31^1+c×31^0
    \]

    而子串"cde"的哈希值是:

    \[c×31^2+d×31^1+e×31^0
    \]

    与母串哈希值不匹配,于是母串向后继续计算哈希值,下标i=3指向字母d,

    \[(a×31^2+b×31^1+c×31^0)×31+d-a×31^3
    \]

    前n个字符的hash * 31-前n字符的第一字符 * 31的n次方(n是子串长度)

    可以计算出母串中"bcd"的哈希值,再与子串哈希值进行比较

代码实现

    public static void main(String[] args) {
String s = "ABABABA";
String p = "ABA";
match(p, s);
}
//p是母串,s是子串
private static void match(String p, String s) {
long hash_p = hash(p);//p的hash值
long[] hashOfS = hash(s, p.length());
match(hash_p, hashOfS);
} private static void match(long hash_p, long[] hash_s) {
for (int i = 0; i < hash_s.length; i++) {
if (hash_s[i] == hash_p) {
System.out.println(i);
}
}
}
final static long seed = 31;
/**
* n是子串的长度
* 用滚动方法求出s中长度为n的每个子串的hash,组成一个hash数组
*/
static long[] hash(final String s, final int n) {
long[] res = new long[s.length() - n + 1];
//前m个字符的hash
res[0] = hash(s.substring(0, n));
for (int i = n; i < s.length(); i++) {
char newChar = s.charAt(i);
char ochar = s.charAt(i - n);
//前n个字符的hash*seed-前n字符的第一字符*seed的n次方
long v = (res[i - n] * seed + newChar - pow(seed, n) * ochar) % Long.MAX_VALUE; //防止溢出
res[i - n + 1] = v;
}
return res;
}
static long pow(long a,int b){
long ans = 1;
while(b>0){
ans*=a;
b--;
}
return ans;
}
/**
* 使用100000个不同字符串产生的冲突数,大概在0~3波动,使用100百万不同的字符串,冲突数大概110+范围波动。
* 如果数据量非常大,可以在子串和母串哈希值匹配成功的时候多进行一步朴素的字符串比较,以防万一。
*/
static long hash(String str) {
long h = 0;
for (int i = 0; i != str.length(); ++i) {
h = seed * h + str.charAt(i);
}
return h % Long.MAX_VALUE;
}

时间复杂度分析

设母串长度为m,子串长度为n。

则滚动计算母串哈希值复杂度是O(m)

计算子串哈希值复杂度是O(n)

遍历母串进行哈希值匹配的复杂度是O(m)

综上,Rabin-Karp算法的时间复杂度是O(m+n)

KMP 算法

概念

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。

主要用于在文本串S中查找模式串P出现的位置。

  • KMP和暴力匹配的不同

  • 如何求解next数组

代码实现

public static void main(String[] args) {
String src = "babababcbabababb";
String p = "bababb";
int index = kmp(src, p);
System.out.println(index);
}
//s是文本串,p是模式串
private static int kmp(String s, String p) {
if (s.length() == 0 || p.length() == 0) return -1;
if (p.length() > s.length()) return -1;
int[] next = next(p);
int i = 0; //文本串的下标
int j = 0; //模式串的下标
int slength = s.length();
int plength = p.length();
while (i < slength) {
//①如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++
//j=-1,因为next[0]=-1,说明p的第一位和i这个位置无法匹配,这时i,j都增加1,i移位,j从0开始
if (j == -1 || s.charAt(i) == p.charAt(j)) {
i++;
j++;
} else {
//②如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]回退
//next[j]即为j所对应的next值
j = next[j];
}
if (j == plength) { //匹配成功了
return i - j;
}
}
return -1;
} private static int[] next(String p) {
int[] next = new int[p.length() + 1];
int left = -1;
int right = 0;
next[0] = -1;
while (right < p.length()) {
if (left == -1 || p.charAt(left) == p.charAt(right)) {
next[++right] = ++left; //最长匹配位置加一
} else {
left = next[left]; //前缀回退到上一个最长匹配位置
}
}
return next;
}

KMP算法改进(nextval数组)

可以把next数组改造成nextval数组

下标(j) 0 1 2 3 4 5 6
模式串(P) a b c d a b d
next -1 0 0 0 0 1 2
nextval -1 0 0 0 -1 0 2

当 j 处模式串字符不等于next[j]处模式串字符时,nextval[j]=next[j]

当 j 处模式串字符等于next[j]处模式串字符时,nextval[j]=nextval[next[j]]

比如:

下标为j=4处的模式串字符是a,而下标为next[j]处的模式串字符也是a,则nextval[4]拷贝nextval[next[4]]处的值,也就是-1

解释一下,按照next数组回退的话,下标为4处next[4]=0,会回退到下标为0处,而下标为0处next[0]=-1,会回退到下标为-1处,回退了两次。

但是如果应用改进的nextval数组,下标为4处next[4]=-1,直接回退到下标为-1处,只需要回退一次。

当遇到有大量连续重复元素的数组时,性能提升最为明显。

比如:

当 j=3 时,通过next数组回退需要先退到下标为2,再退到下标为1,在退到下标为0,最后退到下标为-1。

而通过nextval数组回退,一次就可以回退到下标为-1处。

//求nextval数组
private static int[] nextval(String p, int[] nextval) {
int right = 0, left = -1; //left是前缀,right是后缀
nextval[0] = -1;
while (right < p.length()) {
if (left == -1 || p.charAt(right) == p.charAt(left)) {
left++;
right++; //多加了一次判断比较 nextval[right] 和 nextval[left]
if (nextval[right] != nextval[left]) {
nextval[right] = left;
} else {
nextval[right] = nextval[left]; //注意
}
} else {
left = nextval[left]; //回退
}
}
return nextval;
}

【数据结构与算法】字符串匹配(Rabin-Karp 算法和KMP 算法)的更多相关文章

  1. 字符串匹配的BF算法和KMP算法学习

    引言:关于字符串 字符串(string):是由0或多个字符组成的有限序列.一般写作`s = "123456..."`.s这里是主串,其中的一部分就是子串. 其实,对于字符串大小关系 ...

  2. 字符串匹配(BF算法和KMP算法及改进KMP算法)

    #include <stdio.h> #include <string.h> #include <stdlib.h> #include<cstring> ...

  3. 字符串匹配-BF算法和KMP算法

    声明:图片及内容基于https://www.bilibili.com/video/av95949609 BF算法 原理分析 Brute Force 暴力算法 用来在主串中查找模式串是否存以及出现位置 ...

  4. 数据结构(十六)模式匹配算法--Brute Force算法和KMP算法

    一.模式匹配 串的查找定位操作(也称为串的模式匹配操作)指的是在当前串(主串)中寻找子串(模式串)的过程.若在主串中找到了一个和模式串相同的子串,则查找成功:若在主串中找不到与模式串相同的子串,则查找 ...

  5. BF算法和KMP算法

    这两天复习数据结构(严蔚敏版),记录第四章串中的两个重要算法,BF算法和KMP算法,博主主要学习Java,所以分析采用Java语言,后面会补上C语言的实现过程. 1.Brute-Force算法(暴力法 ...

  6. 串匹配模式中的BF算法和KMP算法

    考研的专业课以及找工作的笔试题,对于串匹配模式都会有一定的考察,写这篇博客的目的在于进行知识的回顾与复习,方便遇见类似的题目不会纠结太多. 传统的BF算法 传统算法讲的是串与串依次一对一的比较,举例设 ...

  7. 串的模式匹配 BF算法和KMP算法

    设有主串s和子串t,子串t的定位就是要在主串中找到一个与子串t相等的子串.通常把主串s称为目标串,把子串t称为模式串,因此定位也称为模式匹配. 模式匹配成功是指在目标串s中找到一个模式串t: 不成功则 ...

  8. BF算法和KMP算法 python实现

    BF算法 def Index(s1,s2,pos = 0): """ BF算法 """ i = pos j = 0 while(i < ...

  9. 软件设计师_朴素模式匹配算法和KMP算法

    1.从主字符串中匹配模式字符串(暴力匹配) 2. KMP算法

随机推荐

  1. mysql left join转inner join

    在日常优化过程中,发现一个怪事情,同一个SQL出现两个完全不一样执行计划,left join 连驱动表都可以变成不一样. 对于left join,如果where条件里有被关联表过滤,left join ...

  2. 关于Excel中表格转Markdown格式的技巧

    背景介绍 Excel文件转Markdown格式的Table是经常会遇到的场景. Visual Studio Code插件 - Excel to Markdown table Excel to Mark ...

  3. 使用VS2017开发APP中使用VUE.js开发遇到打包出来的android文件 在低版本的android(4.3)中无法正常使用

    使用VS2017开发VUE的APP应用遇到的问题集合 1,  打包出来的apk文件在Android 6.0版本以上手机可以正常打开,在Android 4.3版本手机上无法打开 原因:一开始猜测是不是V ...

  4. 【译】Go:程序如何恢复?

    原文:https://medium.com/a-journey-with-go/go-how-does-a-program-recover-fbbbf27cc31e ​ 当程序不能正确处理错误时, 会 ...

  5. CRM的职能和主要构成模块探索

    CRM客户管理系统是随着互联网的快速发展和信息技术的进步而发展起来的,是企业管理客户关系.优化业务流程的首选.客户关系管理可以存储企业获得的客户信息,方便业务人员随时查看和了解客户需求:客户关系管理可 ...

  6. SpringBoot集成logback后访问日志端点

    问题描述 使用SpringBootAdmin(sba)监控Springboot服务时,配置了logback日志框架,按天滚动生成日志,此时在sba的日志监控页面出现404,如下图所示: 解决方案 查看 ...

  7. error more than one devices and emulator

    问题秒速 莫名的多了一个设备,然后再输入adb shell 解决方法: 1.如果确实有多种设备,要指定设备号 adb -s 设备号 shell(设备号在这里是 emulator-5554,其他同理) ...

  8. NTFS安全权限

    一.NTFS权限概述 1.通过设置NTFS权限,实现不同的用户访问不同的对象的权限 2.分配了真确的访问权限后,用户才能访问其资源 3.设置权限防止资源被篡改.删除 二.文件系统概述 文件系统即在外部 ...

  9. python 12篇 mock接口之flask模块

    一.使用pip install flask按照flask模块. import flask,json # 轻量级web开发框架 server = flask.Flask(__name__) @serve ...

  10. WIN10技巧

    1.快速打开"开始---自动启动"文件夹:开始--支行--shell:startup 2