洗牌/采样/随机数

Reference

Index

洗牌算法

Fisher–Yates shufflearrow-up-right - Wikipedia

Knuth-Durstenfeld Shuffle(Fisher–Yates Shuffle 改进版)

  • Knuth-Durstenfeld Shuffle 是一个“原地”(in-place)算法

  • 伪代码

    To shuffle an array a of n elements (indices 0..n-1):

  • Python 实现

  • 正确性证明

    • 即证明:任何一个元素 shuffle 之后出现在任意位置的概率都是 1/N

    • 证明:

Inside-Out Shuffle

  • Inside-Out Shuffle 算法会返回一个打乱的副本

  • 伪代码

  • Python 实现

  • 正确性证明 TODO

Inside-Out Shuffle 无穷版

  • 所谓无限,指的是 n=len(src) 未知的情况

    蓄水池采样

  • Python 实现

采样(等概率)

问题描述

无放回思路

  • 无放回会改变原始数据,如果不想修改原始数据,需要额外复制一份

思路 1:类似 Knuth-Durstenfeld Shuffle

  • 跟洗牌一样,随机从序列中取出一个元素,同时从原序列中删除

  • 该方法需要修改原数据

  • 如果不能修改原数据,可以考虑复制一份数据(仅当 n 比较小时)

  • 证明 TODO

思路 2:类似 Inside-Out Shuffle

  • Python random.sample()当 `n <= 4ceil(log(3m, 4))` 时*的采用的方法

  • 类似 Inside-Out 算法的方式从原数组中抽取 m 个数

有放回思路

思路

  • Python random.sample()当 `n > 4ceil(log(3m, 4))` 时*采用的策略

  • 使用一个 set 记录已经被抽到的位置,如果该位置已经存在,则继续

  • 如果 n < 4**ceil(log(3*m, 4)) 时采用这个策略,可能会产生类似 Hash 冲突的问题,导致调用随机数方法的次数过多

    求解调用rangd()的次数是一个期望问题

    蓄水池抽样及实现arrow-up-right - handspeaker - 博客园

蓄水池采样

问题描述

基本思路 -> 等概率采样

蓄水池采样算法

  • 伪代码

  • Python 实现

  • 证明:该算法保证每个元素以 m/n 的概率被选中

    • n <= m 时,每个元素被 100% 选中

    • n > m 时,

      • n=m+1时,根据算法定义,第m+1个元素被选中的概率=m/m+1,而前m个元素被选中的概率=1-被第m+1个元素替换的概率=1-(m/m+1)*(1/m)=m/m+1;说明前m+1个元素被选中的概率相等。

      • n=m+k时,第m+k个元素被选中的概率=m/m+k=m/n,前(n-1)个元素被选中的概率=1-被第n个元素替换的概率=1-(m/n)*(1/m)=m/n;说明所有n个元素被选中的概率相等。

        蓄水池抽样及实现arrow-up-right - handspeaker - 博客园

采样(不等概率)

查表法(有放回)

  • 图示

    • 上面的线段按频度进行分割,下面为等距分割

    • 往下方的线段随机打点(均匀分布),根据点所落在的区间对应到上方的线段,来确定采样的对象

      Word2Vec 中负采样使用的方法

  • Python 实现

  • 测试

如何采样,使 n-1 被采样 n 次?

  • 0 被采样 1 次,1 被采样 2 次,2 被采样 3 次,...

法 1

  • 类似查表法的思路

法 2

  • Python

无放回 TODO

随机数

rand_m() 生成 rand_n()

面试中随机数"等概率"vs"不等概率"生成问题arrow-up-right - hellogiser - 博客园

说明:

  • rand_n() 能够等概率生成 [0, n) 内的整数,rand_m() 同理

假设 rand_m() 由以下方式实现

m > n

  • 这种情况比较简单

  • 因为 m >= n,只要用 rand_m() 生成的数在 [0, n) 范围就返回,反之重新生成

m < n

  • 以下方法要求 n <= m*m

  • 先看代码再说思路(暂时忽略注释的代码):

  • 思路

    • rand_m(m) * m + rand_m(m) 的意思是先等概率生成 0, m, 2m, .. (m-1)*m,然后在加上 rand_m(m);最终效果相当于等概率生成 [0, m*m) 之间的数

    • 然后再按照 m > n的做法生成 n

  • 注意到,如果 n << m*m 时,这个方法会产生大量废操作

  • 一个优化方法就是等概率生成 t*n 中的数,然后对 n 取模

利用不等概率方法等概率产生随机数

问题 1

  • non_rand_01() 测试代码

  • Python

问题 2

  • 计算 n 的二进制位数,记为 k;然后在问题 1 的基础上,产生长度为 k 的随机 0/1 数列

  • 注意,产生的随机数可能大于 n,当大于 n 时,则丢弃并重新生成

最后更新于

这有帮助吗?