如何利用一个randn()【表示得到从1到n的随机数】改造为一个randm()的函数?
rand()函数要求的是每个位置出现等概率。
首先会想到使用 乘 + if判断
的解法,以rand3实现rand7为例:
while(true) {
int s = rand3() * 3 - 2; //因为原数至少为2,所以不满足条件,需要减掉2
return s;
}
乍一看,确实只会返回1到7的整数,
但是假设原来rand3()获取每个位置的概率是1/3,这现在获取1的概率必须要三次都取得1,即(1/3)^3
而要取得4【即三个整数和为6】,可以是2 2 2,也可以是1 2 3的任何一种排列(共6种),即取得4的概率是取得1概率的7倍,这显然不符合每个位置取得概率相同的定义。
解题方法
(1)将srand = rand3() - 1,使得变成0, 1, 2随机取;
(2)将srand 扩大到刚好容纳一个rand3() - 1的程度,即扩大三倍
(3)将srand * 3 + rand3() - 1
(4)筛选结果,若在0 ~ 6中, + 1返回;否则再来一次
public int rand3_To_rand7() {
while (true) {
int srand = (rand3() - 1) * 3;
int sum = srand + rand3() - 1;
if (sum >= 7) { //模板:保證每個7都出現相同的概率,即8,9的存在會使得1,2概率上升。
continue;
} else {
return sum + 1; //對七取余会导致得不到7,需要整体右移一位
}
}
}
分析:
将srand扩大三倍,会随机生成0 3 6这三个数,每个未知的空位刚好为3(空位需要带上自己)
对其随机加上0 1 2,就会使得出现res: 0 1 2 3 4 5 6 7 8的概率相等
当res >= 7时,说明超过了0 ~ 6 七个数的范围,剔除。
返回res + 1
对于其他复杂一点的情况,只需适当求余即可,如rand5()实现rand7():
public int rand5_To_rand7(){
while (true) {
int srand = rand5() - 1;
int s = srand * 5;
int sum = s + rand5() - 1;
if (sum >= 21) {
continue;
} else {
return sum % 7 + 1;
}
}
}
总结:
关键步骤是扩大间隙,要扩大到刚好可以装得下n - 1个数的位置即可。
在返回时,确定范围必须满足所求随机数的最大值的整数倍,使得取得每个位置概率相等。