惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

F
Fox-IT International blog
Recent Announcements
Recent Announcements
D
Docker
IT之家
IT之家
B
Blog
Jina AI
Jina AI
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 【当耐特】
Google DeepMind News
Google DeepMind News
F
Fortinet All Blogs
量子位
C
Check Point Blog
Microsoft Azure Blog
Microsoft Azure Blog
罗磊的独立博客
博客园 - 司徒正美
李成银的技术随笔
美团技术团队
Blog — PlanetScale
Blog — PlanetScale
雷峰网
雷峰网
The GitHub Blog
The GitHub Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
J
Java Code Geeks
T
The Blog of Author Tim Ferriss
酷 壳 – CoolShell
酷 壳 – CoolShell
MongoDB | Blog
MongoDB | Blog
P
Proofpoint News Feed
L
LangChain Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Y
Y Combinator Blog
大猫的无限游戏
大猫的无限游戏
有赞技术团队
有赞技术团队
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
Visual Studio Blog
T
Tailwind CSS Blog
H
Help Net Security
Engineering at Meta
Engineering at Meta
小众软件
小众软件
B
Blog RSS Feed
Stack Overflow Blog
Stack Overflow Blog
月光博客
月光博客
M
Microsoft Research Blog - Microsoft Research
宝玉的分享
宝玉的分享
人人都是产品经理
人人都是产品经理
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
GbyAI
GbyAI
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Last Week in AI
Last Week in AI
Martin Fowler
Martin Fowler
Stack Overflow Blog
Stack Overflow Blog

博客园_首页

mysql备份恢复详解 HAProxy 学习总结 Mysql事物的持久性及原子性 应用内隐私信息被窥视?防窥保护自动感知一键防护 uni-app 实现视频聊天、屏幕分享,支持Android、HarmonyOS、iOS 做共享目录实时同步,踩过这些坑 华为公司发布半导体演进新范式 - “韬(τ)定律”(Tau Law) Linux时区修改为CST Go 语言入门学习笔记基础版 给热水器装上“电量显示”:用 Shelly Gen4 脚本实现零改装水量预测 踩坑实录:接口正常Feign调用字段值为空 耿同学学术打假,就是学术版《狂人日记》;学术打假,就是清扫垃圾 FastApiAdmin 后端接口开发好了,前端管理界面怎么调用与显示? 我写了 50 个 Claude Code Skill 才发现,前 30 个都白写了 告别 "cd /var/log" !用 journalctl 统一掌控 Linux 日志 我用自己的微信聊天记录,微调了一个“数字分身” AI运动APP开发的常见问题集锦一 复盘梳理-如何深入并抽象 告别手动复制!公众号文章批量导出工具,极致提升内容运营效率 【学习笔记】《Python编程 从入门到实践》第2章:变量命名规则、字符串操作与数值类型详解 Docker--Docker简介及系统架构 别再瞎搞 AI 了!大厂AI业务落地的五个关键环节!(建议新手直接照搬) [MAF的Agent管道详解-01]塑智能体边界,从AIAgent抽象类开始 平台智能化到了分水岭:为什么配置代码化才是 AI Coding 的下一代接口 P.4文本统计工具 高光谱拼接算法(二)Harris 角点探测 - 哥布林学者 Claude Code “悄悄”装了 Python 包?别再让它“投错胎”了 - only赟 影刀 vs 八爪鱼 RPA:到底选哪个?一篇讲透 AI Coding开始进入第四个时代,我还没上车呢! 【Agentic RL / 强化学习 / OPD】OpenClaw-RL 源码阅读笔记 --- (1)---基础 完整学习LLM(四):Token是什么 LitCTF2026web部分wp CAD子系统,是自研还是外包? 什么是教程地狱?5个信号说明你已经陷入(附3步摆脱方法) polygon出题教程 Manim物理模拟:别自己写欧拉了! AI 学习笔记:Agent 的应用演示 - 凌杰 分享一个CAN报文编辑器软件 洛谷P13016 [GESP202506 六级] 最大因数 MiniCPM-V 4.6 部署实战:基于 GPUStack 与 SGLang 的端侧多模态模型部署 用 FRP 打通云服务器与本地 Ubuntu,让 Codex 远程调试本地硬件 软考 - 架构设计师 知识点总结 给 FastApiAdmin 加个“会议纪要”模块,我把后端二次开发的坑踩了个遍 聊一聊 MES系统如何实现多种标签打印并支持不同打印机 2026第四届LitCTF网络安全挑战赛Pwn的wp 断尺问题:戴德金分割现实悖论 给句子做个“语义审计”:从词向量到句子向量的方法论 当AI“卡壳”在生产环境:MCP Server 如何帮我们破局 ofdkit-harmony 0.2.0 发布:鸿蒙原生 OFD 阅读库,已上架 ohpm 有了AI测试工具,还需要掌握Playwright、Pytest、Selenium这些框架吗? 组织转型实录——我把传统研发团队改成AI驱动,踩了无数坑 为什么 AI Coding 难进生产环境?深入了解 Everything-Claude-Code ! 到底 TMD 用哪个: npm, pnpm, Yarn, Bun, Deno? 傻瓜, 当然用 npm 啦 上周热点回顾(5.18-5.24) [对比学习LangChain和MAF-04]针对消息的设计 TrueAsync Server 为 PHP 带来了原生的高性能 HTTP 服务器 规则漂移 帆软市场部为什么能成为高人效增长系统? 22. LangChain LCEL,用 | 串联AI的魔法语言 - 老陈说编程 完整学习LLM(二):大模型到底是什么 洛谷-P11942 [KTSC 2025] 重塑矩阵 题解 哈哈哈哈哈打不过我吧,没有办法我(vllm)就是这么强大! Hermes Edu Skills 从 170 到 188:一次中文教育 Agent Skill Pack 的工程化升级 一个外行,半年搞定机械臂:我的从0到1踩坑实录 新写了个直播录制工具,可录制抖音快手斗鱼直播 15天学会AI应用开发(一)搭建AI大模型应用开发环境 Childhood,23款童年卡牌游戏复刻 Github Copilot配置GPT5.5报错:'temperature' does not support 0.1 with this model. Only the default (1) value is supported. - Eric zhou 单曲循环 ClassIn 在 Linux 下无法播放音频 把 TeXstudio / LaTeX 工程交给 AI:texstudio-mcp 功能详解 .NET 8 Web开发入门(六):Blazor 全栈开发——告别 JavaScript 焦虑 别让 LLM 写文件:一套 Agent 进度跟踪的工程化范式 - BurningFish Qt Bridges for C# 深度技术解析 Multus 多网卡方案:IPVLAN 模式 被流量逼出来的架构:从一台服务器到云原生的 17 次蜕变 —— 集群、缓存、MQ、微服务、Docker、K8S 的前世今生 Claude Code安装全流程 Windows保姆级教程 awk 命令练习(从入门到进阶) Java + Spring实现Hermes Agent之龙虾、Skills、Mcp和沙箱代码执行环境思路 轨迹的蓝图:方程求解与交点计算 Agent新技术分享-Forge论文已被ACM接受 PowerMem 记忆系统的遗忘设计,从神经元到代码工程 我用了FastApiAdmin后,连夜把踩过的坑都整理出来了 一个程序员眼中的 AI 核心概念,讲透 LLM 、Agent 、MCP 、Skill 、RAG... 网络安全在线就能打的内网靶场推荐 & Dawn Breaker 单域靶场 WP CTF 中如何用提示词发挥大模型的最大实力:从聊天助手到大手子 PyTorch KernelAgent 源码解读 ---(6)--- Composer 高光谱拼接算法(一)扫推式成像和航带拼接算法 一文看懂fofa常用语法,告别混淆,精准打击! 从零搭建量化投资系统:用 Qlib 一行代码搞定均线分析 企业 AI 落地,第一件事不是买模型,而是建好企业知识库 如何在Oracle Agent Factory中配置国内厂商的LLM? Codex 换模型太麻烦?这个开源桌面工具帮你一键切换 Avalonia中的动画 2026软考|十大管理超全通俗笔记,备考闭眼记! rv1126b内置phy接hub交换机芯片 React 可拖拽列宽 + 点击行选中 ProTable 封装笔记 五大实锤证据:AI不会终结低代码,只会倒逼技术进化 【硬核脑洞】16位实模式最后的疯狂:我们能否在 640KB 常规内存里手搓一个 MD 模拟器? 基于.Net的NetCoreKevin框架中AgentFramework实现AI智能体Skill和工具动态管理和加载
在影子里验证比较对象:随机指纹和哈希的数学原理
Ofnoname · 2026-05-26 · via 博客园_首页

程序里经常会遇到一种看似朴素、实际很贵的问题:两个东西是不是一样?

它可能是两个字符串、两个文件、两个集合、一段文本里的子串,或者三个矩阵是否满足 \(A \cdot B = C\)。如果对象本身很大,尤其是在对象需要跨机器通信、被反复比较、以流式方式到达,或者比较结果只需要高概率可靠时,直接逐个字节比较往往不是最想要的方案。

随机指纹法处理的正是这类问题。它不试图完整保留对象,而是把对象映射成一个短得多的值,然后比较这些短值。这个短值可能丢失信息,所以它不是压缩意义上的可逆编码,而更像一个可快速比较的影子。

影子不能替代实体,但如果影子不同,实体一定不同;如果影子相同,实体也许相同,也许只是碰巧投在了同一个地方。随机性的作用,是让这种“碰巧”变得可控。

指纹与哈希

指纹和哈希函数有相似之处。设对象集合为 \(\mathcal{O}\),我们从一组映射 \(\mathcal{H}\) 中随机选择一个函数 \(f\),然后用 \(f(O)\) 作为对象 \(O\) 的指纹。对于两个对象 \(O_1\)\(O_2\)

  • 如果 \(O_1 = O_2\),那么必然有 \(f(O_1) = f(O_2)\)
  • 如果 \(O_1 \ne O_2\),那么大多数 \(f \in \mathcal{H}\) 都能让 \(f(O_1) \ne f(O_2)\)

第二点是关键。对于输出空间远小于输入空间的固定哈希函数,理论上总会存在碰撞;在非密码学场景下,如果对手知道函数,也可能针对性地寻找碰撞。随机选择函数后,对手很难提前知道我们会拿哪一把尺子去量对象。随机化并不是为了神秘,而是为了把最坏情况变成概率问题。

在算法分析里,能区分 \(O_1\)\(O_2\) 的函数常被称为 witness,也就是“见证者”。如果某个 \(h \in \mathcal{H}\) 满足 \(h(O_1) \ne h(O_2)\),那么 \(h\) 就见证了 \(O_1 \ne O_2\)。指纹法的设计目标,就是让见证者足够多,同时让每个指纹足够短。

这两个目标天然冲突。指纹越短,比较越快,通信越省,但碰撞空间也更大;指纹越长,错误概率更低,却离“直接发送原对象”越来越近。许多随机化算法的味道,就藏在这个 trade-off 里。

用随机素数给二进制串取指纹

一切复杂对象都可以映射成二进制数,而一切二进制串又可以看成整数。给定 \(x \in \{0,1\}^n\),令 \(\mathrm{Number}(x)\) 表示它对应的二进制整数。例如 \(1011_2\) 对应十进制的 \(11\)

随机选择一个素数 \(p\),定义:

\[\mathrm{Finger}_p(x) = \mathrm{Number}(x) \bmod p \]

这个定义非常朴素,但足够有力。如果有 \(x\)\(y\) 不同,那么 \(\mathrm{Number}(x)-\mathrm{Number}(y)\) 是一个非零整数。若它们在模 \(p\) 下指纹相等,则有:

\[\mathrm{Number}(x) \equiv \mathrm{Number}(y) \pmod p \]

也就是:

\[p \mid \left(\mathrm{Number}(x)-\mathrm{Number}(y)\right) \]

所以,错误只会发生在随机选中的 \(p\) 刚好是这个差值的某个素因子时。一个非零整数的素因子数量有限,而可选素数集合可以通过扩大范围来增加。

于是,只要 \(p\) 的可选范围越大,坏素数所占比例就会越低。这就是模素数指纹法的基本逻辑:不是保证没有碰撞,而是让碰撞需要满足一个很具体、很稀疏的条件。

相等判断

考虑一个通信问题。机器 RI 有一个字符串 \(x \in \{0,1\}^n\),机器 RII 有一个集合 \(U=\{u_1,u_2,\ldots,u_k\}\),其中每个 \(u_i\) 也是长度为 \(n\) 的二进制串。RI 不知道 \(U\),RII 不知道 \(x\),它们要判断 \(x \in U\)

最直接的方法是 RI 把整个 \(x\) 发过去,通信量是 \(n\) bit。随机指纹法可以把通信量降到 \(O(\log n)\),代价是引入单边错误。

RI:
  choose random prime p from PRIME(n^2)
  s = Number(x) mod p
  send (p, s) to RII

RII:
  for each u_i in U:
      q_i = Number(u_i) mod p

  if s is equal to some q_i:
      output "x may be in U"
  else:
      output "x is not in U"

这里 \(\mathrm{PRIME}(n^2)\) 表示不超过 \(n^2\) 的素数集合。因为 \(p \le n^2\),发送 \(p\) 需要约 \(2\log n\) bit;又因为 \(s < p\),发送 \(s\) 也需要约 \(2\log n\) bit。所以总通信复杂度大约是 \(4\log n\),也就是 \(O(\log n)\)

这个协议的正确性方向很清楚。如果 \(x \in U\),设 \(x=u_j\),那么对任何 \(p\) 都有 \(\mathrm{Finger}_p(x)=\mathrm{Finger}_p(u_j)\),协议一定会输出“在集合中”。

错误只可能发生在 \(x \notin U\) 时。此时某个 \(u_i\) 可能满足:

\[\mathrm{Number}(x) \equiv \mathrm{Number}(u_i) \pmod p \]

也就是一次碰撞。

\(\mathrm{PRIME}(n^2)\) 的质数选取范围下,错误的概率具体有多大呢?设 \(x\notin U\)。对每个 \(u_i\),令

\[\Delta_i = \left|\mathrm{Number}(x)-\mathrm{Number}(u_i)\right| \]

因为 \(x\ne u_i\),所以 \(\Delta_i\ne 0\);又因为二者都是 \(n\) 位二进制串,所以 \(\Delta_i < 2^n\)。如果 \(x\)\(u_i\) 在模 \(p\) 下发生碰撞,那么 \( p\mid \Delta_i \)

而数论知识还告诉我们,一个小于 \(2^n\) 的非零整数最多有 \(n\) 个不同的素因子。所以,对一个固定的 \(u_i\),能造成碰撞的坏素数最多有 \(n\) 个。对所有 \(k\) 个元素,坏素数总数最多有 \(kn\) 个。

另一方面,不超过 \(n^2\) 的素数个数约为

\[\pi(n^2) \sim \frac{n^2}{\ln(n^2)} = \frac{n^2}{2\ln n} \]

所以随机选中坏素数的概率最多约为

\[\frac{kn}{\pi(n^2)} \approx \frac{kn}{n^2/(2\ln n)} = \frac{2k\ln n}{n} \]

这符合直觉:元素数量 \(k\) 越多,越容易冲突;而当质数范围取 \(n^2\) 时,规模越大越不容易冲突。当

\[k \le \frac{n}{4\ln n} \]

时,错误概率就至多约为 \(\frac 12\)

为什么 \(\frac 12\) 这个节点这么重要呢?它重要是因为,一旦单次错误概率被压到某个小于 \(1\) 的常数,就可以通过独立重复把错误概率指数级压低。重复 \(t\) 轮,每一轮都重新选择独立的随机素数,并且只有每一轮都发生碰撞才会误判,那么错误概率最多变成:

\[2^{-t} \]

随着次数增加,错误概率会迅速降低;而只要问题规模较大,\(k\) 次实验发送的 \(4k\log n\) bit 仍然远小于 \(n\) 个全部比特。

这种协议叫 one-sided-error Monte Carlo protocol。它会很快给出答案,但答案只有一个方向是绝对可靠的:如果它说 \(x \notin U\),那一定是真的;如果它说 \(x \in U\),则可能是碰撞造成的假阳性。

集合不交:碰撞机会按成对数量累积

上面可以问题可以推广。RI 有 \(V=\{v_1,v_2,\ldots,v_l\}\),RII 有 \(U=\{u_1,u_2,\ldots,u_k\}\),目标是判断 \(U \cap V\) 是否为空。

那么思路仍然没有变化,RI 选择一个随机素数 \(p\),计算自己每个元素的指纹并发送,RII 也计算自己每个元素的指纹,并和 RI 发送的指纹比较。直接发送整个集合需要 \(O(ln)\) bit。使用随机指纹时,RI 只需要发送一次 \(p\),再发送 \(l\) 个元素的模 \(p\) 指纹,通信量变为 \(O(l\log n)\) bit。

如果 \(U \cap V \ne \emptyset\),真实公共元素一定会产生相同指纹,所以不会漏报。若 \(U \cap V = \emptyset\),协议可能因为某一对 \((u_i,v_j)\) 的碰撞而误报“有交集”。

这里的错误风险不再只和 \(k\) 有关,而是和 \(k \cdot l\) 有关,因为每一对元素都可能成为碰撞源。若 \(U\cap V=\emptyset\),那么每一对 \((u_i,v_j)\) 都是不相等的。对固定一对元素,碰撞要求:

\[p\mid \left(\mathrm{Number}(u_i)-\mathrm{Number}(v_j)\right) \]

这个非零差值仍然小于 \(2^n\),所以最多有 \(n\) 个不同素因子可能造成碰撞。

现在这样的元素对一共有 \(kl\) 个,因此所有可能导致误报的坏素数总数最多是 \(kln\)。随机从 \(\mathrm{PRIME}(n^2)\) 中选 \(p\),错误概率最多约为:

\[\frac{kln}{\pi(n^2)} \approx \frac{2kl\ln n}{n} \]

因此,只要

\[|U| \cdot |V| = o\left(\frac{n}{\ln n}\right) \]

错误概率就会随着 \(n\) 增大而趋近于 \(0\)。如果希望把单次错误概率压到大约不超过 \(1/2\),可以要求:

\[|U| \cdot |V| \le \frac{n}{4\ln n} \]

在对象对数太多时,碰撞机会会累积。想继续压低错误概率,要么增加指纹长度,要么重复多轮独立随机试验。这里体现的是一个非常常见的原则:单个比较的碰撞概率再低,也会被大量比较通过 union bound 放大。

从 Monte Carlo 到 Las Vegas

上面的协议是 Monte Carlo 型的:它运行很快,但在某个方向上允许小概率错误。另一种常见改造是 Las Vegas 型:先用随机指纹做便宜的初筛,如果指纹已经不同,就直接给出确定的否定答案;如果指纹相同,则回到原对象上做一次完整验证。

这样一来,算法不再出错。因为所有可能出错的“指纹相同”情形,都会被后续的完整验证拦住。

可以把期望成本写成一个简单公式。设随机指纹阶段的成本为 \(T_f\),完整验证的成本为 \(T_v\),假阳性概率为 \(\varepsilon\)。在真实答案是否定的情况下,只有发生碰撞时才会进入完整验证,所以期望成本为:

\[T_f+\varepsilon T_v \]

例如成员判断中,如果 \(x\notin U\),初筛大多数时候会直接发现“不在集合中”。只有当 \(x\) 和某个 \(u_i\) 的指纹碰撞时,才需要让 RI 发送完整的 \(x\),或者用其他确定性方式检查。因此期望通信量可以写成:

\[O(\log n)+\varepsilon O(n) \]

如果通过扩大素数范围或重复独立试验把 \(\varepsilon\) 压得足够低,那么第二项就会变小。

但 Las Vegas 改造并不是免费午餐。在真实答案是肯定的情况占多数下,例如 \(x\in U\),真实相等的元素必然产生相同指纹,于是不管如果选取质数,完整验证通常一定会发生。这样也就退化成了全部比较。

Monte Carlo 用小概率错误换取始终很快;Las Vegas 消除了错误,但把部分情形的完整成本放进了期望复杂度里。适合哪一种,取决于应用更不能接受错误,还是更不能接受偶尔变慢。

Freivalds 算法:不乘矩阵也能验证矩阵乘法

再看一个例子。给定三个 \(n\times n\) 矩阵 \(A,B,C\),要判断 \(A\cdot B=C\)。如果直接计算 \(A\cdot B\) 需要 \(O(n^3)\) 时间。而 Freivalds 算法把验证降到了 \(O(n^2)\)

随机选择一个向量 \(\alpha \in \{0,1\}^n\),然后计算:

\[\beta = A\cdot(B\cdot \alpha), \qquad \gamma = C\cdot \alpha \]

如果 \(\beta \ne \gamma\),输出 \(A\cdot B \ne C\);如果 \(\beta=\gamma\),接受 \(A\cdot B=C\),或者说认为它们“可能相等”。

这里就利用了“不计算,只验证”的思想。直接计算 \(A\cdot B\) 是复杂的。但是,矩阵乘向量只需要 \(O(n^2)\),所以 \(B\cdot \alpha\)\(A\cdot(B\cdot\alpha)\)\(C\cdot\alpha\) 都只需要 \(O(n^2)\)。整个过程不需要显式算出完整的 \(A\cdot B\)

  • 如果 \(A\cdot B=C\)。那么对任何 \(\alpha\) 都有 \(A\cdot B\cdot\alpha=C\cdot\alpha\),算法不会误判为不相等。

  • \(A\cdot B \ne C\)。令 \(D=A\cdot B-C\),则 \(D\ne 0\)。我们希望随机选出的 \(\alpha\)\(D\alpha \ne 0\)

为什么至少一半的 \(\alpha\) 能发现错误?因为 \(D\) 至少有一行非零,记为 \(d=(d_1,\ldots,d_n)\)。存在某个 \(d_j\ne 0\)。固定 \(\alpha\) 中除 \(\alpha_j\) 外的其他坐标后,表达式 \(d\cdot\alpha\) 可以看作关于 \(\alpha_j\) 的一次式:

\[d_j\alpha_j + c \]

其中 \(c\) 由其他坐标决定。由于 \(d_j \ne 0\),在 \(\alpha_j \in {0,1}\) 的两个选择中,不可能两个都让它等于 \(0\)。如果 \(c=0\),那么 \(\alpha_j=1\) 时值为 \(d_j\ne 0\);如果 \(c\ne 0\),也至多只有一个选择会把它抵消为 \(0\)

于是,在所有 \(2^n\) 个 0-1 向量中,至少 \(2^{n-1}\) 个能让这一行的内积非零,从而让 \(D\alpha\ne 0\)

所以,一次运行的错误概率最多是 \(1/2\)。独立重复 \(t\) 次,并且只有每次都通过才接受 \(A\cdot B=C\),错误概率最多降为 \(2^{-t}\),复杂度变为 \(O(tn^2)\)。当 \(t\) 是几十这样的常数时,这在工程上已经非常强。

这个算法的美感在于,它并没有计算要验证的对象本身,而是随机观察它在某个方向上的投影。若两个矩阵真的不同,它们在至少一半的 0-1 方向上会表现出差异。

更多例子

很多看似不同的算法,都在使用同一个原则:不直接比较对象本身,而是比较它们在某个随机选择下的影子。

Rabin-Karp 字符串匹配

要在长文本 \(T\) 中寻找模式串 \(P\),朴素方法会把 \(P\)\(T\) 的每个等长窗口逐字符比较。Rabin-Karp 则先比较窗口的指纹。

如果指纹不同,窗口一定不匹配;如果指纹相同,再逐字符确认。由于相邻窗口只差一个字符,指纹还可以滚动更新,不必每次从头计算。这就是随机指纹在字符串算法中的典型用法:它不直接证明两个字符串相等,而是快速排除大量“不可能相等”的候选。

参考:字符串问题的歪门奇宝:进制哈希

多项式恒等测试

给定两个多项式 \(P(x)\)\(Q(x)\),要判断它们是否完全相同。直接展开可能很贵,但可以随机选一个点 \(r\),比较:

\[P(r) \quad \text{和} \quad Q(r) \]

如果 \(P=Q\),那么对所有 \(r\) 都相等。若 \(P\ne Q\),则 \(P-Q\) 是一个非零多项式。一个次数为 \(d\) 的非零多项式最多只有 \(d\) 个根。因此,只要随机点来自足够大的集合,误判概率就不高。

集合测试

集合也可以被编码成多项式。给定集合 \(S={s_1,\ldots,s_m}\),定义:

\[P_S(z)=\prod_{i=1}^m(z-s_i) \]

如果两个集合 \(S\)\(T\) 相等,那么 \(P_S=P_T\)。如果集合不同,则两个多项式不同。随机选择一个点 \(r\),比较 \(P_S(r)\)\(P_T(r)\),就能用一个短值随机验证两个集合是否相等。

这些例子的共同点是:对象不同的时候,能让它们“看起来相同”的随机选择只占少数。随机性不是为了制造不确定,而是为了把潜在碰撞压缩到一个可计算、可放大的概率界里。

总结

随机指纹适合“否定容易、肯定昂贵”的场景。两个指纹不同,通常可以立刻否定;两个指纹相同,则要看算法类型决定是否接受。成员判断和矩阵验证中的协议选择接受小概率错误,因此是 Monte Carlo;如果指纹相同后回查原文,就变成 Las Vegas。

它不适合无条件要求零错误、且又不能回查原对象的场景。比如安全认证里不能把普通随机哈希当作密码学承诺;面对主动攻击者时,也不能只靠短指纹断言两个对象完全相同。这里讨论的是算法意义上的随机化验证,不是密码学安全。

实现层面还有几个现实细节。随机源要足够独立,否则重复试验未必能按 \(2^{-t}\) 衰减;模运算要注意溢出和负数;当对象数量很大时,单个碰撞概率再小也会被 union bound 放大。很多“理论上概率很低”的事件,在海量系统里会变成迟早发生的事件。