首先,出一個簡單的題目:有一個大小為100的數組,裡面的元素是從 1 到 100,怎樣随機從裡面選擇 1 個數呢?
很簡單,利用 `Math.random() * 100` ,就可以拿到一個 0 到 99 的随機數,然後去數組找對應的位置就行了。
下面難度提高一點哈! 有一個大小為100的數組,裡面的元素是從 1 到 100,怎樣随機從裡面選擇 50 個數呢?注意!不能重複。
如果根據上面的思路,你一定想到了:是不是随機 50 次就行了?
那麼問題來了,這 50 次你是不能保證都是不能重複的,對吧!
嗯,你是不是又有靈感了,弄一個數組,把每一次随機的數都放到數組裡,下一次随機就看這個數組裡面有沒有這數,有的話就繼續随機,直到這個數組裡面有 50 個數字就停止。完美!
嗯,講道理的話,這樣做也沒有問題,肯定可以實作題目要求。但是如果我們想一下,就會發現問題:如果我們考慮極限,就比如拿 99 個數字(你不要跟我說:那就考慮相反的情況,拿 1 個數字,把這個數字去掉就行>。< ),是不是越往後重複的機率越高,那麼需要重複随機的次數也越大?
是的,小老弟,你很聰明,一下子就了解了。我們可以看下代碼:
var idxArr = []
while(idxArr.length < 99){
let idx = parseInt(Math.random()*100)
var time = 0
while(idxArr.includes(idx)) {
idx = parseInt(Math.random()*100)
console.log('time', time++)
}
idxArr.push(idx);
}
複制
代碼很簡單,如果随機的數字不在數組裡面,就 push 進去,否則就重新随機。這裡用了一個 times 來計算每一個數需要随機的次數。可以看下輸出:

可以看到,一開始基本是沒有重複的,但是到了後面最後一個,要拿到一個沒有出現過的随機數需要多達 65 次。
如果你把數組再放大一點,結果會更加誇張。當然,現實裡不會有這麼極端的情況,就像你說的,要拿 99 個數就反過來剔除 1 個數就行了。但是呢,就算沒有 99 也可能有 50 對吧,這個性質還是一樣的,就是可能重複較多。
這個時候就需要換一個思路,如果先将數組裡面的元素打亂,那麼按順序選擇前 50 個不就可以了?
是的!
但我們得注意什麼叫亂?
一副撲克有 54 張牌,有 54! 種排列方式。所謂的打亂指的是,你所執行的操作,應該能夠 等機率地生成 這 54! 種結果中的一種。
是以呢,進入主題了:洗牌算法。
原理很簡單:最後一個數和前面任意 n-1 個數中的一個交換,然後倒數第二個數和前面任意 n-2 個數中的一個交換。。。依次下去就行了。
直接看代碼:
public void shuffle(int[] array) {
int length = array.length;
Random random = new Random();
int temp;
while (length > 0) {
int index = random.nextInt(length);
temp = array[index];
array[index] = array[length -1];
array[length -1] = temp;
length--;
}
}
//[1,2,3,4,5,6,7,8,9]
//輸出[6 1 7 8 5 2 4 9 3 ]
複制
在整個過程中,這個算法保證了每一個元素出現在每一個位置的機率是相等的。
這個算法真的很牛逼很經典,而且代碼很少。