V

算法可视化

ZHEN
分享到微博分享到 XGitHub 仓库个人主页
学习工具算法复杂度速查算法学习路径
数据结构数组链表栈队列树堆哈希表图字典树并查集LRU 缓存跳表线段树B+ 树布隆过滤器树状数组
经典排序算法冒泡排序鸡尾酒排序双调排序选择排序插入排序二分插入排序希尔排序归并排序自顶向下归并快速排序三路快排双轴快排堆排序计数排序基数排序桶排序
图算法Dijkstra 最短路Kruskal 最小生成树Prim 最小生成树Bellman-Ford 最短路拓扑排序Floyd 多源最短路强连通分量2-SAT最大流二分图匹配LCA 倍增欧拉路径
动态规划编辑距离0-1 背包完全背包最长公共子序列最长递增子序列硬币找零方案数石子合并旅行商 TSP树形 DP数位 DP换根 DP
回溯与搜索N 皇后子集生成全排列组合总和迷宫寻路岛屿数量单词搜索数独A* 寻路
字符串KMP 字符串匹配Rabin-KarpBoyer-MooreManacher后缀数组LCP / height 数组AC 自动机Z 函数
数学与数论埃氏筛线性筛欧几里得算法快速幂扩展欧几里得中国剩余定理欧拉函数米勒-拉宾FFTPollard's Rho
计算几何凸包旋转卡壳最近点对线段相交扫描线求交
查找二分查找二分边界旋转数组搜索二分答案三分查找

Rabin-Karp 字符串匹配

字符串 · 滚动哈希

用一个「哈希」代替逐字符比

在文本 abcabcab 里找模式 cab。KMP 靠部分匹配表跳过冗余比较,Rabin-Karp 则换个思路:给每个长度 m 的窗口算一个哈希值(把这段字符压成一个数),拿它和模式的哈希比。哈希不等,这段绝不可能匹配,直接跳过;只有哈希相等时,才逐字符验证一次。

滚动哈希:O(1) 更新

把字符看成数字(a=1, b=2, c=3),用多项式哈希:hash = (…(s₀·B + s₁)·B + … + sₘ₋₁) mod M(这里 B=10, M=997)。模式 cab 的哈希是 3·100 + 1·10 + 2 = 312。关键在滚动:窗口右移一格时,不用重算整段——去掉最左字符的贡献、乘上 B、加上新进来的字符,一步 O(1) 就得到新窗口的哈希。于是整趟扫描只花 O(n + m)(平均)。

下面是 T=abcabcab、P=cab(哈希 312)。点「下一步」逐步看:浅蓝色是当前窗口,变量面板显示窗口哈希 vs 模式哈希。哈希不等就跳过(很快);哈希相等(下标 2、5 处都是 312)就逐字符验证,通过则命中、整段标绿。右侧代码随每一步同步高亮。

T
a
b
c
a
b
c
a
b
P
c
a
b

先算模式哈希:hash("cab") = 312;从第 0 个窗口开始,每格只比一个哈希数

1function rabinKarp(text: string, pat: string): number[] {
2 const n = text.length, m = pat.length;
3 const B = 10, M = 997;
4 const val = (ch: string) => ch.charCodeAt(0) - 96;
5 let patHash = 0, winHash = 0, pow = 1;
6 for (let k = 0; k < m; k++) {
7 patHash = (patHash * B + val(pat[k])) % M; // 模式哈希
8 winHash = (winHash * B + val(text[k])) % M; // 首窗口哈希
9 if (k < m - 1) pow = (pow * B) % M;
10 }
11 const res: number[] = [];
12 for (let i = 0; i + m <= n; i++) {
13 if (winHash === patHash) { // 哈希命中
14 if (text.slice(i, i + m) === pat) { // 逐字符验证
15 res.push(i);
16 }
17 }
18 if (i + m < n) { // 滚动更新 O(1)
19 winHash = ((winHash - val(text[i]) * pow) * B + val(text[i + m])) % M;
20 winHash = ((winHash % M) + M) % M;
21 }
22 }
23 return res;
24}
文本 Tabcabcab
模式 Pcab
模式哈希312
窗口哈希123
已找到—
1 / 12

哈希会「撞车」,所以要验证

两段不同的字符可能算出同一个哈希(哈希冲突)——所以哈希相等只是「可能匹配」,必须逐字符验证才能确认,否则会误报。
用大素数模 + 随机基能把冲突概率压到极低;工程里也常用双哈希(两个独立哈希都相等才验证)。
本页用小模数 997 是为了让哈希是好读的三位数;此例恰好没有冲突。

滚动哈希不止用于单模式匹配:它是多模式匹配、最长重复子串、文件/文本指纹去重的通用工具。与 KMP 对比——KMP 用「模式的结构」(部分匹配表)省比较,Rabin-Karp 用「哈希」把比较压成比一个数,两条不同的加速路。