我看到了这段代码,以随机排列列表:
public static void shuffle(List<Integer> numbers) {
if(numbers == null || numbers.isEmpty()) return;
for(int i = 0; i < numbers.size(); ++i) {
int index = (int) (i + Math.random()*(numbers.size() - i));
swap(numbers, i, index);
}
}
该代码似乎可以正常工作,但我不理解此代码段:
int index = (int) (i + Math.random()*(numbers.size() - i));
基本上是
i + R*(n-i)
,但这如何确保:i)我们不会超出范围索引,或者ii)我不会更改同一元素,即index == i
,随机播放不会那么随机吗? 最佳答案
Math.random()
在间隔[0, 1)
中返回一个统一的随机数,并且numbers.size() - i
最好将该数字缩放为间隔[0, numbers.size() - i)
。例如,如果i
为2并且列表的大小为5,则在理想情况下,以这种方式选择间隔[0, 3)
中的随机数。最后,将i
添加到数字中,并且(int)
强制转换将丢弃数字的小数部分。因此,在此示例中,随机生成[2, 5)
中的随机整数(即2、3或4),以便在每次迭代中,索引X处的数字与其自身或在其之后的数字互换。
但是,这里有一个重要的微妙之处。由于浮点数的性质和缩放数字时的舍入误差,在极少数情况下,即使Math.random()*(numbers.size() - i)
输出不包含1的数字,numbers.size() - i
的输出也可能等于Math.random()
。导致习惯用法Math.random()*(numbers.size() - i)
偏向某些结果。例如,只要numbers.size() - i
无法将2 ^ 53整除,就会发生这种情况,因为Math.random()
在后台使用java.util.Random
,并且其算法会生成53位精度的数字。因此,Math.random()
并不是编写此代码的最佳方法,并且该代码本来可以使用专门为生成随机整数而设计的方法(例如nextInt
的java.util.Random
方法)。另请参见this question和this question。
编辑:事实证明,Math.random() * integer
习惯用法不会产生可能会返回integer
的问题,至少当integer
是任何正int
且使用舍入到最近舍入模式时Java。请参见this question。