随机数生成算法:K进制逐位生成+拒绝采样

随机数生成算法:K进制逐位生成+拒绝采样

转自:【宫水三叶】k 进制诸位生成 + 拒绝采样

基本分析

给定一个随机生成 1 ~ 7 的函数,要求实现等概率返回 1 ~ 10 的函数。

首先需要知道,在输出域上进行定量整体偏移,仍然满足等概率,即要实现 0 ~ 6 随机器,只需要在 rand7 的返回值上进行 -1 操作即可。

但输出域的 拼接/叠加 并不满足等概率。例如 rand7() + rand7() 会产生 [2, 14] 范围内的数,但每个数并非等概率:

  • 产生 2 的概率为: 1 7 × 1 7 = 1 49 \frac{1}{7}\times\frac{1}{7}=\frac{1}{49} 71×71=491
  • 产生 4 的概率为: 1 7 × 1 7 + 1 7 × 1 7 + 1 7 × 1 7 = 3 49 \frac{1}{7}\times\frac{1}{7}+\frac{1}{7}\times\frac{1}{7}+\frac{1}{7}\times\frac{1}{7}=\frac{3}{49} 71×71+71×71+71×71=493

在 [2,14] 这 13 个数中,等概率的数不足 10 个。

因此,你应该知道「执行两次 rand7 相加,将 [1, 10] 范围内的数进行返回,否则一直重试」的做法是错误的。

k进制逐位生成 + 拒绝采样

上述做法出现概率分布不均的情况,是因为两次随机值的不同组合「相加」的会出现相同的结果,比如 (1, 3)、(2, 2)、(3, 1) 最终结果均为 4。

结合每次执行 rand7 都可以看作一次独立事件。我们可以将两次 rand7 的结果看作生成 7 进制的两位。从而实现每个数值都唯一对应了一种随机值的组合(等概率),反之亦然。

举个例子,设随机执行两次 rand7 得到的结果分别是 4(第一次)、7(第二次),由于我们是要 7 进制的数,因此可以先对 rand7 的执行结果进行 -1 操作,将输出域偏移到 [0,6](仍为等概率),即得到 3(第一次)和 6(第二次),最终得到的是数值 ( 63 ) 7 (63)_7 (63)7 ,数值 ( 63 ) 7 (63)_7 (63)7 唯一对应了我们的随机值组合方案,反过来随机值组合方案也唯一对应一个 7 进制的数值。

那么根据「进制转换」的相关知识,如果我们存在一个 randK 的函数,对其执行 n n n 次,我们能够等概率产生 [ 0 , K n − 1 ] [0, K^n - 1] [0,Kn1] 范围内的数值。

回到本题,执行一次 rand7 只能产生 [0,6] 范围内的数值,不足 10 个;而执行 2 次 rand7 的话则能产生 [0, 48] 范围内的数值,足够 10 个,且等概率。

我们只需要判定生成的值是否为题意的 [1, 10] 即可,如果是的话直接返回,否则一直重试。

代码:

class Solution {
public:
    int rand10() {
        while (1) {
            int r71 = rand7();
            int r70 = rand7();
            int val = (r71 - 1) * 7 + r70 - 1;
            if (val >= 1 && val <= 10) return val;
        }
    }
};
  • 时间复杂度:期望复杂度为 O(1),最坏情况下为 O( ∞ \infty )
  • 空间复杂度:O(1)

进阶

降低对 rand7 的调用次数

我们发现,在上述解法中,范围 [0, 48] 中,只有 [1, 10] 范围内的数据会被接受返回,其余情况均被拒绝重试。

为了尽可能少的调用 rand7 方法,我们可以从 [0, 48] 中取与 [1, 10] 成倍数关系的数,来进行转换。

我们可以取 [0, 48] 中的 [1, 40] 范围内的数来代指 [1, 10]。

首先在 [0, 48] 中取 [1, 40] 仍为等概率,其次形如 x1 的数值有 4 个(1、11、21、31),形如 x2 的数值有 4 个(2、12、22、32)… 因此最终结果仍为等概率。

代码:

class Solution {
public:
    int rand10() {
        while (1) {
            int r71 = rand7();
            int r70 = rand7();
            int val = (r71 - 1) * 7 + r70 - 1;
            if (val >= 1 && val <= 40) return val % 10 + 1;
        }
    }
};

时间复杂度:期望复杂度为 O(1),最坏情况下为 O( ∞ \infty )
空间复杂度:O(1)

计算 rand7 的期望调用次数

在 [0, 48] 中我们采纳了 [1, 40] 范围内的数值,即以调用两次为基本单位的话,有 40 49 \frac{40}{49} 4940 的概率被接受返回(成功)。

成功的概率为 40 49 \frac{40}{49} 4940 ,那么触发成功所需次数(期望次数)为其倒数 49 40 = 1.225 \frac{49}{40} = 1.225 4049=1.225,每次会调用两次 rand7,因而总的期望调用次数为 1.225 ∗ 2 = 2.45 1.225 * 2 = 2.45 1.2252=2.45

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值