[英]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.