我看到了这段代码,以随机排列列表:

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()并不是编写此代码的最佳方法,并且该代码本来可以使用专门为生成随机整数而设计的方法(例如nextIntjava.util.Random方法)。另请参见this questionthis question

编辑:事实证明,Math.random() * integer习惯用法不会产生可能会返回integer的问题,至少当integer是任何正int且使用舍入到最近舍入模式时Java。请参见this question

07-24 09:46
查看更多