均匀随机抽样:洗牌与蓄水池
数一批对象有多少个,与在其中等概率取一个,是同一个问题的两面。前者给出 、 这些计数,后者要求每个对象出现的概率都精确等于计数的倒数。洗牌是最小的例子: 张牌的排列有 种,正确的洗牌算法必须让每种排列的概率都是 ,一种都不能多、一种都不能少。
1 · 洗牌算法与选择序列的双射
Fisher–Yates 洗牌(Fisher–Yates shuffle)从后往前扫:对 ,在 里取一个随机下标 ,交换第 位与第 位。
均匀性不必逐个排列去算概率,数一数算法自己有多少种走法就够了。它读入 个随机数,第 步的取值范围有 个,选择序列共 条。每条序列跑出一个排列,且不同序列跑出的排列必不相同:末位上的元素由第一步唯一决定,倒数第二位由第二步唯一决定,逐位反推即可从结果恢复出整条序列。于是这是 条序列到 个排列的双射(见双射与双计数)。既然每条序列等概率,每个排列也就等概率。
这条论证把随机性问题化归成了计数问题,用的还是 排列 P(n, k) 那一套: 个位置逐格收缩相乘。区别只在于,排列的枚举要逐个列出全部 个排列,而洗牌只在其中取一个。
2 · 每步与全体交换的偏差
有一种写起来更顺手的版本:对 ,在整个 里取随机下标 并交换。它的选择序列有 条。若各排列等概率, 必须被 整除,而 时这不可能。 为奇素数时 是奇数、 是偶数; 为合数时取伯特兰素数 ,它整除 ,却大于 的任何素因子,故不整除 。整除性一旦失败, 条等概率序列就摊不成 份相等的概率。
偏差有多大可以直接穷举。 时把 条序列逐条跑完:排列 占 ,排列 只占 ,而理想值是 ,最高与最低相差 倍。固定种子 、、 万次实测与之吻合: 出现 次(理想值的 倍), 出现 次( 倍),拟合优度 ;同一组种子下 Fisher–Yates 的 ,与自由度 相称。
警示 · 流传较广的说法是错误版本「偏向恒等排列」,穷举给出的却是相反的结果。恒等排列的占比在 分别为理想值的 、、 倍,一直低于理想值,到 才升到 倍。被压得最狠的始终是轮转 :命中它的选择序列数在 到 依次为 ,即 。这条式子本页没有找到文献出处,只做到穷举验证 。
3 · 随机的 k 子集
取一个大小为 的随机子集,不必洗完整副。部分洗牌只跑前 步,第 步在 里取一位换到第 位,取完前 位即止,时间是 ;把被换走的位置记在一张稀疏表里,空间也是 ,不必先物化长度 的数组。
另一条路更短: 子集共 个,取一个 内的随机整数,再把它解码成对应的子集即可,见组合对象的编号。两者的代价不同——部分洗牌只做 次交换,解码要算 个组合数,但后者只消耗一个随机数,在随机数昂贵或需要按编号复现某次抽样时更合用。
4 · 蓄水池抽样
数据流的长度事先未知,甚至大到装不进内存,此时无法先数出总数再抽。蓄水池抽样(reservoir sampling)只扫一遍:前 个元素直接进池;此后第 个元素以 的概率进池,进池时随机踢掉池中的一个。
定理 4.1 处理完流中前 个元素后,这 个元素中的每一个留在池中的概率都是 ()。
证明 对 归纳。 时全部入池,概率为 。设结论对 成立,考察第 个元素到达后的池。新元素入池的概率按算法就是 。对任一旧元素,它此刻在池中当且仅当此前在池中,且没有在这一步被踢出。此前在池中的概率是 ;这一步被踢出,要求新元素入池(概率 )且踢中的恰是它(概率 ),合为 。两事件独立于旧元素的身份,故它留下的概率为
与新元素一致,归纳成立。∎
结论对流的每个前缀都成立,因此在流的任何一个位置中断,池中都是一份合格的均匀样本。这一点是蓄水池抽样与部分洗牌的实质差别:后者要先知道 。
5 · 参考文献
- Fisher–Yates shuffle. Wikipedia. 洗牌算法的现代形式、复杂度与常见错误变体。https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle
- Reservoir sampling. Wikipedia. 流式等概率抽样与 Algorithm R 的描述。https://en.wikipedia.org/wiki/Reservoir_sampling
- Vitter, J. S. (1985). Random sampling with a reservoir. ACM Transactions on Mathematical Software, 11(1), 37–57.