Rabin-Karp算法是一种基于哈希函数的字符串匹配算法,由 Michael O. Rabin 和 Richard M. Karp 于1987年提出,核心思想是用哈希函数将模式串和文本串中的子串转换为数值进行比较,避免大量不必要的字符比较。这个算法特别适合多模式串匹配场景,时间复杂度平均为O(n+m),n是文本串长度,m是模式串长度。
Rabin-Karp算法的关键在于使用滚动哈希函数(Rolling Hash),它可以在常数时间内计算出滑动窗口的新哈希值,保证算法在大多数情况下的高效性。
核心特性
接下来大家一起看下Rabin-Karp算法的部分主流语言实现:
public class RabinKarp {
private final static int PRIME = 101; // 哈希计算使用的质数
public static int search(String text, String pattern) {
int m = pattern.length();
int n = text.length();
if (m > n) return -1;
if (m == 0) return 0;
// 计算哈希乘数,等于d^(m-1) % PRIME,用于滚动哈希计算
int h = 1;
for (int i = 0; i
比如使用更复杂的哈希函数来减少冲突
public class ImprovedRabinKarp {
private final static long PRIME1 = 1000000007; // 第一个哈希的质数
private final static long PRIME2 = 1000000009; // 第二个哈希的质数
// 使用双哈希来减少冲突
public static int search(String text, String pattern) {
int m = pattern.length();
int n = text.length();
if (m > n) return -1;
if (m == 0) return 0;
// 计算哈希乘数
long h1 = 1;
long h2 = 1;
for (int i = 0; i
1)文档相似度检测和抄袭检测
2)网络安全中的特征码匹配
3)多模式字符串搜索引擎
4)编译器中的词法分析器
Rabin-Karp算法的一个变种应用于文件相似度比较
public class RabinKarpFingerprint {
private final static long PRIME = 1000000007;
private final static int WINDOW_SIZE = 5; // 指纹窗口大小
public static Set generateFingerprints(String text) {
Set fingerprints = new HashSet();
int n = text.length();
if (n fingerprints1 = generateFingerprints(text1);
Set fingerprints2 = generateFingerprints(text2);
// 计算交集大小
Set intersection = new HashSet(fingerprints1);
intersection.retainAll(fingerprints2);
// 计算并集大小
Set union = new HashSet(fingerprints1);
union.addAll(fingerprints2);
// 杰卡德相似度系数
return (double) intersection.size() / union.size();
}
private static long calculateHash(String str, int length) {
long hash = 0;
for (int i = 0; i
一些编程竞赛里也使用Rabin-Karp思想进行高效的子字符串查询
public class SubstringHash {
private static final long PRIME = 1000000007;
private static final int BASE = 256;
private long[] hash; // 前缀哈希值
private long[] pow; // BASE的幂
private String s; // 源字符串
public SubstringHash(String s) {
this.s = s;
int n = s.length();
hash = new long[n + 1];
pow = new long[n + 1];
// 预计算BASE的幂
pow[0] = 1;
for (int i = 1; i
Rabin-Karp算法巧妙结合哈希计算和滚动窗口技术,在字符串匹配领域提供了一种高效的解决方案,特别适合多模式匹配和大规模文本处理场景。
Boyer-Moore算法是一种高效的字符串匹配算法,由 Robert S. Boyer和J Strother Moore 设计于1977年。它从右向左比较字符,并利用两个启发式规则(坏字符规则和好后缀规则)在不匹配情况下实现较大跳跃,减少比较次数。Boyer-Moore算法在实际应用中大部分情况下比朴素算法和KMP算法更高效。
核心特性:
public class BoyerMoore {
private final int R; // 字符集大小
private int[] badChar; // 坏字符表
private int[] goodSuffix; // 好后缀表
private int[] borderPos; // 边界位置表
private String pattern; // 模式串
public BoyerMoore(String pattern) {
this.R = 256; // ASCII字符集
this.pattern = pattern;
int m = pattern.length();
// 初始化坏字符表
badChar = new int[R];
for (int c = 0; c 0) {
while (j = 0; j--) {
if (pattern.charAt(j) != text.charAt(i + j)) {
// 坏字符规则
skip = Math.max(1, j - badChar[text.charAt(i + j)]);
// 好后缀规则
if (j
对于一些应用场景,可以只使用坏字符规则,简化算法实现
public class SimplifiedBoyerMoore {
private final int R; // 字符集大小
private int[] badChar; // 坏字符表
private String pattern; // 模式串
public SimplifiedBoyerMoore(String pattern) {
this.R = 256; // ASCII字符集
this.pattern = pattern;
int m = pattern.length();
// 初始化坏字符表
badChar = new int[R];
for (int c = 0; c = 0; j--) {
if (pattern.charAt(j) != text.charAt(i + j)) {
// 仅使用坏字符规则
skip = Math.max(1, j - badChar[text.charAt(i + j)]);
break;
}
}
if (skip == 0) return i; // 找到匹配
}
return -1; // 没有找到匹配
}
}
针对需要重复搜索同一模式串的场景,可以预计算并缓存结果
public class CachedBoyerMoore {
private Map cache = new HashMap();
public int search(String text, String pattern) {
// 检查缓存中是否有预计算的Boyer-Moore对象
BoyerMoore bm = cache.get(pattern);
if (bm == null) {
bm = new BoyerMoore(pattern);
cache.put(pattern, bm);
}
return bm.search(text);
}
}
1)文本编辑器的查找功能
2)网络安全中的特征码匹配
3)自然语言处理中的关键词检索
4)大规模文本数据处理
Horspool算法是Boyer-Moore的简化版本,只使用坏字符规则,但是对坏字符表进行了修改
public class Horspool {
private final int R; // 字符集大小
private int[] badChar; // 坏字符表
private String pattern; // 模式串
public Horspool(String pattern) {
this.R = 256; // ASCII字符集
this.pattern = pattern;
int m = pattern.length();
// 初始化坏字符表
badChar = new int[R];
// 所有字符默认移动模式串长度
for (int c = 0; c n) return -1;
int i = m - 1; // 从模式串最后一个字符对齐开始
while (i
Sunday算法是另一种Boyer-Moore的变种,它关注的是文本串中模式串后面的字符
public class Sunday {
private final int R; // 字符集大小
private int[] shift; // 移动表
private String pattern; // 模式串
public Sunday(String pattern) {
this.R = 256; // ASCII字符集
this.pattern = pattern;
int m = pattern.length();
// 初始化移动表
shift = new int[R];
// 所有字符默认移动模式串长度+1
for (int c = 0; c n) return -1;
int i = 0; // 从文本串开始位置
while (i = n) {
return -1;
}
// 使用Sunday算法的移动规则
i += shift[text.charAt(i + m)];
}
return -1; // 没有找到匹配
}
}
KMP(Knuth-Morris-Pratt)算法是一种高效的字符串匹配算法,核心思想是利用已经部分匹配的信息,避免重复比较,在文本串中快速查找模式串。KMP算法特别适合处理长文本和重复性高的模式串,时间复杂度是O(m+n),m是模式串长度,n是文本串长度。
KMP算法的关键在于构建一个部分匹配表(也叫失败函数或者next数组),这个表记录了当匹配失败时,模式串指针应该回退到的位置,让算法跳过已知不可能匹配的位置,提高匹配效率。
KMP算法主要分为两个阶段:
核心特性:
public class NaiveStringMatcher {
/**
* 朴素字符串匹配算法
* @param text 文本串
* @param pattern 模式串
* @return 匹配成功则返回模式串在文本串中的起始位置,否则返回-1
*/
public static int naiveSearch(String text, String pattern) {
int n = text.length();
int m = pattern.length();
// 特殊情况处理
if (m == 0) return 0;
if (n
上述实现暴力枚举所有可能的匹配位置,逐一比较文本串与模式串的每个字符,直到找到完全匹配或确定不存在匹配
public class KMP {
// 构建部分匹配表(next数组)
private static int[] buildNext(String pattern) {
int m = pattern.length();
int[] next = new int[m];
next[0] = 0; // 第一个字符的最长相等前后缀长度为0
for (int i = 1, j = 0; i 0 && pattern.charAt(i) != pattern.charAt(j)) {
j = next[j - 1];
}
// 当前字符匹配,j向前移动
if (pattern.charAt(i) == pattern.charAt(j)) {
j++;
}
// 记录当前位置的最长相等前后缀长度
next[i] = j;
}
return next;
}
// KMP搜索算法
public static int kmpSearch(String text, String pattern) {
if (pattern == null || pattern.length() == 0) {
return 0;
}
if (text == null || text.length() 0 && text.charAt(i) != pattern.charAt(j)) {
j = next[j - 1];
}
// 当前字符匹配,j向前移动
if (text.charAt(i) == pattern.charAt(j)) {
j++;
}
// 完全匹配,返回起始索引
if (j == m) {
return i - m + 1;
}
}
return -1; // 未找到匹配
}
// 查找所有匹配位置
public static List kmpSearchAll(String text, String pattern) {
List positions = new ArrayList();
if (pattern == null || pattern.length() == 0) {
return positions;
}
if (text == null || text.length() 0 && text.charAt(i) != pattern.charAt(j)) {
j = next[j - 1];
}
// 当前字符匹配,j向前移动
if (text.charAt(i) == pattern.charAt(j)) {
j++;
}
// 完全匹配,记录位置并继续匹配
if (j == m) {
positions.add(i - m + 1);
// 回退j以寻找下一个匹配
j = next[j - 1];
}
}
return positions;
}
public static void main(String[] args) {
String text = "ABABDABACDABABCABAB";
String pattern = "ABABCABAB";
int pos = kmpSearch(text, pattern);
List allPos = kmpSearchAll(text, pattern);
System.out.println("文本: " + text);
System.out.println("模式: " + pattern);
System.out.println("首次匹配位置: " + (pos != -1 ? pos : "未找到"));
System.out.println("所有匹配位置: " + allPos);
// 打印next数组,帮助理解
int[] next = buildNext(pattern);
System.out.print("next数组: ");
for (int val : next) {
System.out.print(val + " ");
}
System.out.println();
}
}
在上述代码中:
// 当前字符不匹配,回退j
while (j > 0 && text.charAt(i) != pattern.charAt(j)) {
j = next[j - 1];
}
是 KMP 算法的核心,在匹配失败时根据预先计算的next数组来确定模式串指针的回退位置。
优化后的 next 数组
// 优化next数组,避免匹配失败后回退到同样会失败的位置
private static int[] buildOptimizedNext(String pattern) {
int m = pattern.length();
int[] next = new int[m];
next[0] = 0;
for (int i = 1, j = 0; i 0 && pattern.charAt(i) != pattern.charAt(j)) {
j = next[j - 1];
}
if (pattern.charAt(i) == pattern.charAt(j)) {
j++;
}
// 当前位置匹配失败时,如果回退位置的字符与当前位置相同,则继续回退
if (i + 1
预处理减少分支实现
// 预处理字符映射,减少字符比较的分支
public static int kmpSearchOptimized(String text, String pattern) {
if (pattern == null || pattern.length() == 0) {
return 0;
}
if (text == null || text.length()
1)生物信息学中的DNA序列匹配
2)网络入侵检测系统中的模式匹配
3)搜索引擎的关键词匹配
4)数据压缩算法中的模式识别
// Aho-Corasick算法 - KMP的多模式扩展
public static class AhoCorasick {
static class TrieNode {
TrieNode[] children = new TrieNode[256];
TrieNode fail;
List patternIndices = new ArrayList();
public TrieNode() {
fail = null;
}
}
private TrieNode root;
private String[] patterns;
public AhoCorasick(String[] patterns) {
this.patterns = patterns;
buildTrie();
buildFailureLinks();
}
private void buildTrie() {
root = new TrieNode();
for (int i = 0; i queue = new LinkedList();
// 初始化根节点的子节点
for (int i = 0; i > search(String text) {
List> results = new ArrayList();
TrieNode currentState = root;
for (int i = 0; i (patternIndex, startPos));
}
}
return results;
}
static class Pair {
K first;
V second;
public Pair(K first, V second) {
this.first = first;
this.second = second;
}
}
}
KMP算法是字符串处理中的经典算法,用来解决字符串匹配问题,理解它对提升算法设计能力还是很有帮助的。
本文来自在线网站:seven的菜鸟成长之路,作者:seven,转载请注明原文链接:www.seven97.top
登录查看全部
参与评论
手机查看
返回顶部