[英]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.Random
的nextInt
方法)。 另见这个问题和这个问题。
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 到 50 个整数的列表。
所以得到一个从 0 到 49 的随机值来索引它。 说是30。
获取索引 30 处的项目。
现在将索引 30 处的项目替换为索引 49 处的项目。
下次生成一个介于 0 到 48 之间的数字。 永远不会达到 49,并且那里的号码占据了最后使用的号码的位置。
继续此过程,直到您用完列表。
注意:表达式(int)(Math.random() * n)
将生成一个介于0
和n-1
之间的随机数,因为Math.random
会生成一个介于0
和1
之间的随机数。
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]
int index = (int) (i + Math.random()*(numbers.size() - i));
- 重要的是要注意 Math.random() 将生成一个属于 <0;1) 的数字。 所以它永远不会超过边界,因为独占最大值将是: i + 1*(number.size() -i) = number.size
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.