簡體   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