如何得到多個不同的隨機數 洗牌演算法

2022-05-01 22:15:12 字數 2207 閱讀 3650

先來思考乙個問題:有乙個大小為 100 的陣列,裡面的元素是從 1 到 100 按順序排列,怎樣隨機的從裡面選擇 1 個數?

最簡單的方法是利用系統的方法math.random() * 100,這樣就可以拿到乙個 0 到 99 的隨機數,然後去陣列找對應的位置就即可。

接下來在思考乙個問題:有乙個大小為100的陣列,裡面的元素是從 1 到 100 按順序排列,怎樣隨機的從裡面選擇 50 個數?

注意數字不能重複!

如果根據上面的思路,你第一想法是:隨機 50 次不就行了?

但是,這樣做有個很明顯的 bug :數字是會重複的。

修改一下?

弄乙個陣列,把每一次隨機出來的數與前面的比較,看是否出現過。

這樣是可以的!

但,還是有個小問題,考慮一下極端情況:有乙個大小為100的陣列,裡面的元素是從 1 到 100 按順序排列,怎樣隨機的從裡面選擇 99 個數

如果按照上面的方法操作,越往後選擇的數字跟前面已經挑選的數字重複的概率越高,這就會造成如果陣列很大,選擇的數字數目也很大的話,重複次數在量級上會很大。

這個時候就需要換乙個思路,如果先將陣列裡面的元素打亂,那麼按順序選擇前 50 個不就可以了?

是的!但我們得注意什麼叫亂?

一副撲克有 54 張牌,有 54! 種排列方式。所謂的打亂指的是,你所執行的操作,應該能夠等概率地生成這 54! 種結果中的一種。

洗牌演算法就能做到這一點。

fisher–yates shuffle演算法由 ronald fisher 和 frank yates 於 1938 年提出,在 1964 年由 richard durstenfeld 改編為適用於電腦程式設計的版本。

這個演算法很牛逼卻很好理解,通俗的解釋就是:將最後乙個數和前面任意 n-1 個數中的乙個數進行交換,然後倒數第二個數和前面任意 n-2 個數中的乙個數進行交換。。。

可以證明,這是等概率生成的乙個排列。

證明:第i次選到元素m概率p = 前i-1個位置選擇元素時沒有選中m的概率 * 第i個位置選中m的概率 連乘可以化成1/n,即

$$\frac * \frac  \cdots \frac * \frac = \frac$$

可見與i無關。

直觀的理解,相當於n個人抽籤,跟先後順序無關,每個人抽到某個元素的概率是相等的。

直觀的程式實現是隨機乙個,將其去除,再隨機再去除,去除要耗時,而上面的交換做到了原地、o(1)去除。

為什麼寫這個演算法呢?因為兩者的概率計算很相似。

給定乙個資料流,資料流長度n很大,且n直到處理完所有資料之前都不可知,請問如何在只遍歷一遍資料(o(n))的情況下,能夠隨機選取出m個不重複的資料。

這個場景強調了3件事:

資料流長度n很大且不可知,所以不能一次性存入記憶體。

時間複雜度為o(n)。

隨機選取m個數,每個數被選中的概率為m/n。

第1點限制了不能直接取n內的m個隨機數,然後按索引取出資料。第2點限制了不能先遍歷一遍,然後分塊儲存資料,再隨機選取。第3點是資料選取絕對隨機的保證。講真,在不知道蓄水池演算法前,我想破腦袋也不知道該題做何解。

網上的解釋沒看懂,還是直接看**吧

int res = new int[m];

// init

for (int i = 0; i < reservoir.length; i++)

for (int i = m; i < datastream.length; i++)

}

我們來計算第i個最終落在蓄水池的概率,分兩種情況:

$i > m$,$p(i最終落在蓄水池) = \frac * \frac * \frac \cdots \frac = \frac$(即第i次被換進去,之後都沒有被換出來),是不是和上面的公式一摸一樣。

另一種情況,i初始就在蓄水池,則$p(i最終落在蓄水池) = 1 * \frac * \frac \cdots * \frac = \frac$(即每次都不被換出去)

因此,每個元素最終落在蓄水池的概率都是 $\frac$.

我覺得最精妙的是,到達任意時間\(t\),前\(t\)個數被取到的概率都是 \(\frac\)

1. 2. 

不使用隨機數的洗牌演算法

氣泡排序改一下都能成為乙個合理的洗牌演算法。include static int a 56 int main for int i 0 i 56 i std cout a i std cout 在演算法不公開的情況下,產生乙個足夠亂的序列。或者說雖然是固定演算法,但黑盒下難以猜出。比如54張牌編號0 ...

如何讓Fortran生成不同的隨機數

用fortran生成隨機數的方法很簡單,就是 call random seed call random number rd 生成隨機數組可以這樣 do k 1,10 call random seed call random number rd x k rd do something end do 但...

如何高效地產生多個不重複的隨機數

型別一 完全範圍內的隨機數 1 int a 100 2 3 1.初始化 按序列號依次賦值 4for int i 0 i 99 i 58 2.生成不同的隨機數序列 9for int i 99 i 1 i 10 上面這段 只需要遍歷一次就可以產生這100個不重複的隨機數.型別二 部分範圍內的多個不同的隨...