学校的资料备份服务器上有 50 万个文件,总大小 20 TB。管理员小周的任务是:找出哪些文件是完全重复的,把多余的删掉。
笨办法:逐字节比较。对每一对文件读进来逐个字节比,50 万 × 50 万 ÷ 2 = 1250 亿次比较,跑到天亮也跑不完。
稍微聪明的办法:比大小。先按文件大小分组,只比大小相同的。这能过滤掉大部分,但同大小的文件(比如两张都是 1 MB 的图片)数量依然很多。
聪明的办法:给每个文件算一个「指纹」。把整个文件的字节序列通过一个固定公式压成一个 64 位整数。指纹相同 → 几乎可以断定文件相同;指纹不同 → 一定不同。
小周兴奋地写完程序,跑完发现:有两个明显不同的文件指纹竟然一样!他查了资料才知道 —— 这就是哈希冲突。指纹只有 2⁶⁴ 种可能,而文件有无数种,撞车是数学必然。
但他也发现:只要把指纹加长到 128 位(两个不同的 64 位公式),冲突概率就低到「比硬盘出错还罕见」。所以真正的工程做法是:接受冲突的可能性,把它压到可以忽略。
字符串哈希 = 给字符串算指纹;双哈希 = 同时用两把尺子量,撞车概率平方级下降。
💡 故事背后的算法思想
把「比较」变成「算数」:字符串比较本来是 O(L)(L 是长度)的操作。哈希把它变成「算一个数」+「比两个数」,如果配合前缀哈希,比较两段子串只需要 O(1)。
多项式哈希的自然性:把字符串 s₁s₂…sₙ 看成 B 进制数 s₁B^(n−1) + s₂B^(n−2) + … + sₙB⁰,这与「整数按位加权求和」完全同构,因此计算机能高效递推计算。
前缀哈希的威力:用 H[i] 表示前 i 个字符的哈希值,则任意子串 [l, r] 的哈希可以用 H[r] − H[l−1] · B^(r−l+1) 一次算出。这就是后面所有「子串判等」类算法的基石。