(f1)Karp-Rabin算法:串即是数
Karp-Rabin:串即是数
看完你能把字符串匹配彻底转化为数字比较,说清哈希滚动的每一步
按 空格/→ 演示下一步
全部页面点击任意一页,跳回舞台从这页播放
Karp-Rabin:串即是数
看完你能把字符串匹配彻底转化为数字比较,说清哈希滚动的每一步
暴力匹配慢在哪
在文本里找关键词,最直接的办法是逐位对齐、逐个字符比对。可一旦中途不匹配,就得往回退一格、重新开始——大部分时间都耗在重复比较上。能不能一次比较就判断整段匹配?
扫一个码对应一整串商品,不必逐位核对每个数字
化串为数:进制编码思想
前页结论:暴力匹配慢在逐字符比较。那有没有办法一次就比完?想想「314」这个数——我们从不逐位比较它,而是直接当成一个整数比大小。串能不能也这样变成一个数?
3、1、4 对应串中各字符,位置即权重
如何把串变成数
把串当作进位数:选进制、定映射、按位展开、求和取值。
化串为数的实现
这段 Python 把「按位展开」写成代码:字符映射成数字,再累乘求和。
ord 把字符变成 0~255 的「数位」;h*BASE+val 正是十进制按位展开的进制版:左移一位、落新位。
凡物皆数
前面几页我们用进制编码把字符串变成了一个大整数,匹配变成了比大小。其实字符串只是特例——任何能序列化的对象,本质都是一串符号,也就都能变成数。
身份证号把一个人的身份信息映射成一个数字
凡物皆数与化串为数
二者名字相近、常被混为一谈,但一个是具体算法技巧,一个是可以带走的思想原则。
- Karp-Rabin 的具体手法
- 进制编码:串映射成整数
- 配合滚动哈希,用在单点
- 可推广到万物的一般原则
- 任意对象都可映射为数
- 换场景可复用:去重、指纹
串亦是数:窗口数值的意义
上一页我们把子串按进制编码成了一个数。现在关键一步:既然串变成了数,判断两个子串是否相同,就不用逐字比对,直接比较两个数字是否相等——相等即匹配。
验明身份不必查本人,比对号码即可
滚动:O(1) 更新指纹
相邻窗口数值的递推关系:减去最高位、乘进制、加新字符,常数时间滑动
滑动窗口数值演化图
沿箭头看窗口右移一位:老数减去最高位贡献、乘进制、再加新字符,一步得到新指纹。
Karp-Rabin 主流程
预处理哈希值,再逐窗口滚动更新,一次比对指纹即可判断匹配。
高亮行串起全流程:先化串为数得两枚指纹,主循环只比指纹;相等时回验防碰撞,随后滚动 O(1) 算出下一窗口,均摊 O(n)。
数太大了怎么办
前一页我们能把整个窗口压成一个数,O(1) 滚动更新——但如果串长 1000,这个数就有上千位,内存根本存不下。怎么办?答案是:只留它的「余数」当指纹。
完整身份号太长,报后四位即可快速比对,但可能撞号
指纹冲突与验证
前页我们给每个窗口算出一个指纹。可指纹只是数:两个人指纹相同,未必是同一个人。数值相等不等于串相等——碰撞必须想清楚怎么处理。
闸机先快速比对脸(指纹),存疑时才人工查证件(逐字符核验)
自测:滚动更新
已知窗口哈希值 h 和新进出的字符,如何 O(1) 得到下一个窗口的哈希值?
收束:串即是数
- ✓把串编码成数,匹配变成数的比较
- ✓预处理指纹后逐位滚动,窗口更新只需 O(1)
- ✓指纹相等只代表可能相等,必须回原串验证
- ✓思想可迁移:任何对象都能设计出可滚动的指纹
课后思考
先自己想一分钟,再看参考答案。开放问题没有唯一解,答案只是思考的起点。
参考答案三步各管一事:乘 base 承接结构,加新位补增量,减高位去残留。直接重算是 O(m),滚动才保住 O(1)。可想想缺一步会怎样错位。
参考答案冲突时会把不匹配窗口当命中,正确性消失——这正是随机化的边界。可进一步想:取多个模数,冲突概率怎样下降?
参考答案先按行做一维哈希,再对指纹列纵向滚动,两遍一维嵌套仍均摊 O(1)。难点是移出元素要两层剥落。还能推广到树或图吗?