问题

如何用一个1-8随机数生成器制作一个1-7随机数生成器?

回答
好的,我们来聊聊怎么把一个1到8的“轮盘”改造成一个1到7的“骰子”。这听起来有点像魔术,但实际上是概率学里一个很有趣的应用。

想象一下你手里有一个能公平地转出1到8任意一个数字的装置,比如一个特别制作的八面骰子或者一个写着1到8数字的转盘。我们的目标是只接受1到7的数字,而8出现的时候,我们就得“重新来过”,直到我们得到一个我们想要的数字为止。

核心思想:过滤无效结果

整个过程就像是我们在玩一个游戏。我们从1到8的装置里抽取数字,就像从一个盒子里抓球。我们想要的是一个1到7的球。如果抓出来的是8,那这个球就不能算数,我们得把它放回去,然后重新抓。这个“放回去重新抓”的过程,就是过滤掉无效结果。

具体步骤拆解:

1. 获取一个1到8的随机数。
这是第一步,也是最基础的一步。无论你用什么方式,只要能确保1到8这八个数字出现的概率是均等的就行。

2. 检查获取到的数字。
拿到这个数字后,我们要做的第一件事就是看它是不是8。
如果数字是1、2、3、4、5、6或7: 恭喜你!你得到了一个有效的数字。这就是我们想要的1到7随机数,直接使用它,然后结束整个过程。
如果数字是8: 这个数字我们就不能用了。它代表了一个“失败”的结果。在这种情况下,我们不能停止,也不能随便丢弃,而是需要重新执行步骤1。

3. 重复直到获得有效结果。
如果上一步拿到的是8,我们就回到第一步,再生成一个新的1到8的随机数。我们不断重复这个过程:生成一个1到8的数,检查它是不是8。只要它不是8,我们就停止并使用这个数。

为什么这样做是公平的?

有人可能会问,这样做会不会影响数字的随机性?会不会让某些数字比其他数字出现的几率更大?

答案是:不会。

我们来看一下概率。在一个1到8的装置里:
数字1出现的概率是 1/8
数字2出现的概率是 1/8
...
数字7出现的概率是 1/8
数字8出现的概率是 1/8

当我们过滤掉8,只接受1到7时,我们是在一个“有效集合”(1, 2, 3, 4, 5, 6, 7)里重新分配概率。

我们生成一个数。如果它是1到7中的一个,我们就用它。
如果它是8,我们就“丢弃”这次尝试,然后重新来。

假设我们进行了很多次尝试。在每一次尝试中,得到1到7中任何一个数字的概率都是7/8,而得到8的概率是1/8。

想象一下我们做了100次1到8的生成:
平均来说,我们会得到1到7大概 100 (7/8) = 87.5 次有效数字。
平均来说,我们会得到8大概 100 (1/8) = 12.5 次。

在这些有效的87.5次结果里,数字1、2、3、4、5、6、7各自出现的次数仍然是大致均等的。为什么?因为每一次我们得到一个1到7的数字,它的“来源”都是公平的1/8。我们只是忽略了那个不想要的8。

换句话说,我们可以把这个过程想象成:我们从一个包含七个写着1到7数字的球和一个写着8的球的袋子里摸球。我们摸到8就放回去重摸,直到摸到1到7的任意一个。这样,摸到1到7每个数字的概率就变成了 1/7。

举个实际的例子:

假设你正在玩一个游戏,你需要掷一个1到8的骰子来决定你的行动。但是,游戏规则是:
掷出17,你就按照数字移动相应步数。
掷出8,你就什么也不做,然后重新掷一次骰子。

你掷出了一个3。太好了,你移动了3步。
你又掷了一个8。没关系,什么也不做,然后你再次掷骰子。这次你掷出了一个5。很好,你移动了5步。

你看到吗?你总是会得到一个1到7的数字来决定你的行动,而那个8只是让你“跳过”了一回合,并没有影响到你得到有效数字的公平性。

总结一下这个“魔法”的步骤:

1. 使用你的18随机数生成器,获取一个随机数。
2. 检查这个数:
如果它在1到7之间,就接受它,然后停止。
如果它是8,就丢弃它,然后回到步骤1。

通过这个简单的过滤机制,你就可以从一个18的公平随机数生成器,构建出一个同样公平的17随机数生成器了。这个方法在很多编程和概率相关的场景中都会用到,它是一种非常基础但有效的概率转换技巧。

网友意见

user avatar

其实最简单可行的方法就是重复调用rand8(),如果抽到8就重抽,直到结果小于8为止。这个方法看起来很简单,但其实是有道理的。它利用了Rejection sampling技术,这是一种常用的采样算法。

如果写成C代码的话,大概是这样:

       int rand7(){     int a;     while ( (a=rand8())==8 );     return a; }      

题注还提到了rand8()的开销比较大,那我们就来分析一下这种基于Rejection sampling方法构造的rand7()的效率。

设成功执行一次rand7()所需要的rand8()的次数为N,N是个随机变量,服从参数为7/8的几何分布,其数学期望E[N]=8/7=1.143,方差D[N]=8/49=0.163,标准差大约是0.404。

从这个结果来看,上述rand7()的平均开销和波动都不算大。执行超过五次rand8()的概率约为十万分之三,而超过十次的概率不足十亿分之一。十亿分之一什么概念?假如你有1GB的数据,你希望针对其中的每一个字节做一个和rand7有关的处理,那么你把整个数据库处理完,估计也就能碰上一次倒霉需要抽十次rand8()。而我认为这种程度的“倒霉”是完全可以接受的。


问题本身就算是答完了,再补充一些内容:

第一、关于楼主提到的“最高效率”,我想只能从信息论的角度给出一个界限。一次rand7的算法所能提供的信息量是log7,而一次rand8是log8,所以无论怎样,平均一次rand7所需要执行的rand8的次数至少为log7/log8=0.9358。我们的算法需要1.143次,效率也还可以。

理论上能接近这个界限的方法大概就是王赟 Maigo基于七、八进制小数转换的方法,但该方法需要生成无穷多位八进制小数才能保证完全准确的rand7()。实际应用中,为了提高rand7()的精度,也不得不多产生一些八进制小数。当所需的rand7()的样本较少时,效率也不一定很高。


第二、简要介绍一下Rejection sampling

Rejection Sampling解决的是这样一类问题:我们希望对目标分布进行采样,但直接构造这样一个随机数生成器可能比较困难。我们就先构造一个简单的随机数生成器,使之能产生与近似的分布,然后再利用如下方法就可以得到的样本:

1、对采样,得到样本。
2、对0~1之间的均匀分布采样,得到样本,如果,则接受这个,否则重复1操作。(这一步其实就是以概率接受)。这里是一个常数,要求对于任意的,有,证明也不太复杂,如果记为随机事件“接纳样本”,则经过筛选的分布其实就是条件概率,简单推导一下。因此接受的样本服从分布。

把这个方法应用到rand7()问题上就得到了开始的算法。

1、调用rand8()进行采样,得到一个整数x
2、如果x等于8,则接受概率为0,扔掉这个样本,如果x等于7,则接受概率为1,保留这个样本。


第三、关于多次采样时样本间的独立性问题。这是随机数生成器的一个重要指标。基于Rejection Sampling的这个算法可以保证每次产生的随机数是独立的,因为新样本的产生完全不依赖现有的结果。有人在评论里问我,如果产生的样本等于8则取上一次输出的结果作为本次结果,可以不可以。回答是不可以,虽然这种方案可以保证每一个样本的边缘分布都是1到7的均匀分布,但是这种方法产生的随机数样本之间是有相关性的。假设本次生成的样本是1,考察下一个样本还是1的条件概率,显然执行rand8()的结果无论是1还是8,输出的结果都是1,因此条件概率等于2/8。而独立性要求这个值等于1/7。所以这种方法不能满足独立性的要求。而且这个方法还有一个问题,需要先执行一次rand7(),拿到第一个样本,才能够保证后续样本的边缘分布都是1到7的均匀分布。


第四、关于能不能保证算法在有限次内结束。这个算法在有限次内结束的概率是1,需要执行无穷多次rand7()的概率是0。但概率是0并不代表不会发生,样本空间里是有这种情况的。而能保证有限次内结束的非近似算法,我想是不存在的。假设算法在M次内结束,那么样本空间就有8的M次方个基本元素,每个基本元素的概率相同。而这个数字又不能被7整除,怕是没办法了。我想这就好比是用尺规三等分任意角,如果能无限次操作的话,也是可以做到的,因为1/3=1/4+1/16+1/64+1/256...。而对于这个问题如果采用Rejection Sampling方法,则是1/7=1/8+1/64+1/512+1/4096...这么搞出来个1/7。

类似的话题

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有