繁体   English   中英

使用 Math rand 进行改组是如何工作的?

[英]How does this shuffling with Math rand work?

我看到这段代码来打乱一个列表:

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*(ni)但这如何确保: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)中的随机 integer,因此在每次迭代中,索引 X 处的数字与自身或与跟随它。

然而,这里有一个重要的微妙之处。 由于浮点数的性质和缩放数字时的舍入误差,在极少数情况下, Math.random()*(numbers.size() - i)的 output 可能等于numbers.size() - i ,即使Math.random()输出一个不包括 1 的数字。舍入误差会导致成语Math.random()*(numbers.size() - i)使某些结果偏向于其他结果。 例如,只要 2^53 不能被numbers.size() - i整除,就会发生这种情况,因为Math.random()在后台使用java.util.Random ,其算法生成的数字精度为 53 位。 因此, Math.random()不是编写此代码的最佳方式,代码可以使用专门用于生成随机整数的方法(例如java.util.RandomnextInt方法)。 另见这个问题这个问题

EDIT: As it turns out, the Math.random() * integer idiom does not produce the issue that it may return integer , at least when integer is any positive int and the round-to-nearest rounding mode is used as in Java. 看到这个问题

  1. 您有一个 1 到 50 个整数的列表。

  2. 所以得到一个从 0 到 49 的随机值来索引它。 说是30。

  3. 获取索引 30 处的项目。

  4. 现在将索引 30 处的项目替换为索引 49 处的项目。

  5. 下次生成一个介于 0 到 48 之间的数字。 永远不会达到 49,并且那里的号码占据了最后使用的号码的位置。

  6. 继续此过程,直到您用完列表。

注意:表达式(int)(Math.random() * n)将生成一个介于0n-1之间的随机数,因为Math.random会生成一个介于01之间的随机数。

Math.random() 总是返回一个介于 0(包括)和 1(不包括)之间的浮点数。 因此,当您执行Math.random()*(numbers.size() - i)时,结果将始终介于 0(含)和ni (不含)之间。

然后在i + Math.random()*(numbers.size() - i)中添加 i 。

现在,如您所见,结果将介于 i(包括)和 n(不包括)之间。

之后,您将其转换为 int。 当你将一个 double 转换为一个 int 时,你会截断它,所以现在 index 的值将介于“i to n - 1”之间(包括两者)。

因此,您不会有 ArrayIndexOutOfBoundsException,因为它总是至少比数组的大小小 1。

然而,index 的值可能等于 i,所以是的,你是对的,一个数字可以与自身交换并保持在那里。 这完全没问题。

我建议您不要使用这种自定义方法,而是使用 OOTB Collections.shuffle 检查此项以了解为Collections.shuffle实现的逻辑。

分析您的代码:

Math.random()返回一个带正号的double精度值,大于或等于0.0且小于1.0

现在,让我们假设numbers.size() = 5并空运行for循环:

When i = 0, index = (int) (0 + Math.random()*(5 - 0)) = (int) (0 + 4.x) = 4
When i = 1, index = (int) (1 + Math.random()*(5 - 1)) = (int) (1 + 3.x) = 4
When i = 2, index = (int) (2 + Math.random()*(5 - 2)) = (int) (2 + 2.x) = 4
When i = 3, index = (int) (3 + Math.random()*(5 - 3)) = (int) (3 + 1.x) = 4
When i = 4, index = (int) (4 + Math.random()*(5 - 4)) = (int) (4 + 0.x) = 4

如您所见,当numbers.size() = 5时,每次迭代中index的值都将保持为4

您的疑问:

这如何确保:i) 我们不会得到越界索引

正如上面已经使用试运行所解释的,它永远不会超出 go。

或 ii) 我不会更改相同元素的 ie index == i 并且随机播放不会那么随机?

swap(numbers, i, index); numbers.size() = 5时,每次将索引i处的元素与索引处的元素4交换。 以下示例说明了这一点:

假设numbers = [1, 2, 3, 4, 5]

When i = 0, numbers will become [5, 2, 3, 4, 1]
When i = 1, numbers will become [5, 1, 3, 4, 2]
When i = 2, numbers will become [5, 1, 2, 4, 3]
When i = 3, numbers will become [5, 1, 2, 3, 4]
When i = 4, numbers will become [5, 1, 2, 3, 4]
  1. int index = (int) (i + Math.random()*(numbers.size() - i)); - 重要的是要注意 Math.random() 将生成一个属于 <0;1) 的数字。 所以它永远不会超过边界,因为独占最大值将是: i + 1*(number.size() -i) = number.size
  2. 这一点是有效的,它可能发生。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM