由抽牌、換牌和插牌衍生出三種洗牌演算法,其中抽牌和換牌分別對應fisher-yates shuffle和knuth-durstenfeld shhuffle演算法。
最早提出這個洗牌方法的是 ronald a. fisher 和 frank yates,即 fisher–yates shuffle,其基本思想就是從原始陣列中隨機取乙個之前沒取過的數字到新的陣列中,具體如下:
1. 初始化原始陣列和新陣列,原始陣列長度為n(已知);
2. 從還沒處理的陣列(假如還剩k個)中,隨機產生乙個[0, k)之間的數字p(假設陣列從0開始);
3. 從剩下的k個數中把第p個數取出;
4. 重複步驟2和3直到數字全部取完;
5. 從步驟3取出的數字序列便是乙個打亂了的數列。
下面證明其隨機性,即每個元素被放置在新陣列中的第i個位置是1/n(假設陣列大小是n)。
證明:乙個元素m被放入第i個位置的概率p = 前i-1個位置選擇元素時沒有選中m的概率 * 第i個位置選中m的概率,即
}時間複雜度為o(n),空間複雜度為o(1),缺點必須知道陣列長度n.inside-out algorithm 演算法的基本思思是從前向後掃瞄資料,把位置i的資料隨機插入到前i個(包括第i個)位置中(假設為k),這個操作是在新陣列中進行,然後把原始資料中位置k的數字替換新陣列位置i的數字。其實效果相當於新陣列中位置k和位置i的數字進行互動。
如果知道arr的lengh的話,可以改為for迴圈,由於是從前往後遍歷,所以可以應對arr數目未知的情況,或者arr是乙個動態增加的情況。
證明如下:
原陣列的第 i 個元素(隨機到的數)在新陣列的前 i 個位置的概率都是:(1/i) * [i/(i+1)] * [(i+1)/(i+2)] *...* [(n-1)/n] = 1/n,(即第i次剛好隨機放到了該位置,在後面的n-i 次選擇中該數字不被選中)。
原陣列的第 i 個元素(隨機到的數)在新陣列的 i+1 (包括i + 1)以後的位置(假設是第k個位置)的概率是:(1/k) * [k/(k+1)] * [(k+1)/(k+2)] *...* [(n-1)/n] = 1/n(即第k次剛好隨機放到了該位置,在後面的n-k次選擇中該數字不被選中)。
上述偽**的意思是:先選中第1到k個元素,作為被選中的元素。然後依次對第k+1至第n個元素做如下操作:void inside_out_shuffle(const vector&arr,vector& res)
{ res.assign(arr.size(),0);
copy(arr.begin(),arr.end(),res.begin());
int k;
for (int i=0;i從n個元素中隨機等概率取出k個元素,n長度未知。它能夠在o(n)時間內對n個資料進行等概率隨機抽取。如果資料集合的量特別大或者還在增長(相當於未知資料集合總量),該演算法依然可以等概率抽樣.
偽**:
init : a reservoir with the size: k
for i= k+1 to n
m=random(1, i);
if( m < k)
swap the mth value and ith value
end for
每個元素都有k/x的概率被選中,然後等概率的(1/k)替換掉被選中的元素。其中x是元素的序號。
proof:
如果m被選中,則隨機替換水庫中的乙個物件。最終每個物件被選中的概率均為k/n,證明如下:每次都是以 k/i 的概率來選擇
例: k=1000的話, 從1001開始作選擇,1001被選中的概率是1000/1001,1002被選中的概率是1000/1002,與我們直覺是相符的。
接下來證明:
假設當前是i+1, 按照我們的規定,i+1這個元素被選中的概率是k/i+1,也即第 i+1 這個元素在蓄水池中出現的概率是k/i+1
此時考慮前i個元素,如果前i個元素出現在蓄水池中的概率都是k/i+1的話,說明我們的演算法是沒有問題的。
對這個問題可以用歸納法來證明:k < i <=n
1.當i=k+1的時候,蓄水池的容量為k,第k+1個元素被選擇的概率明顯為k/(k+1), 此時前k個元素出現在蓄水池的概率為 k/(k+1), 很明顯結論成立。
2.假設當 j=i 的時候結論成立,此時以 k/i 的概率來選擇第i個元素,前i-1個元素出現在蓄水池的概率都為k/i。
證明當j=i+1的情況:
即需要證明當以 k/i+1 的概率來選擇第i+1個元素的時候,此時任一前i個元素出現在蓄水池的概率都為k/(i+1).
前i個元素出現在蓄水池的概率有2部分組成, ①在第i+1次選擇前得出現在蓄水池中,②得保證第i+1次選擇的時候不被替換掉
①.由2知道在第i+1次選擇前,任一前i個元素出現在蓄水池的概率都為k/i
②.考慮被替換的概率:
首先要被替換得第 i+1 個元素被選中(不然不用替換了)概率為 k/i+1,其次是因為隨機替換的池子中k個元素中任意乙個,所以不幸被替換的概率是 1/k,故
前i個元素(池中元素)中任一被替換的概率 = k/(i+1) * 1/k = 1/i+1
則(池中元素中)沒有被替換的概率為: 1 - 1/(i+1) = i/i+1
綜合① ②,通過乘法規則
得到前i個元素出現在蓄水池的概率為 k/i * i/(i+1) = k/i+1
故證明成立
證明:第m個物件被選中的概率=選擇m的概率*(其後元素不被選擇的概率+其後元素被選擇的概率*不替換第m個物件的概率),即
洗牌演算法shuffle
對這個問題的研究始於一次在群裡看到朋友發的洗牌面試題。當時也不知道具體的解法如何,於是隨口回了一句 每次從剩下的數字中隨機乙個。過後找相關資料了解了下,洗牌演算法大致有3種,按發明時間先後順序如下 一 fisher yates shuffle 演算法思想就是從原始陣列中隨機抽取乙個新的數字到新陣列中...
洗牌演算法shuffle
原文 一 fisher yates shuffle 演算法思想就是從原始陣列中隨機抽取乙個新的數字到新陣列中。fisher yates shuffle 1.從還沒處理的陣列 假如還剩k個 中,隨機產生乙個 0,k 之間的數字p 假設陣列從0開始 2.從剩下的k個數中把第p個數取出 3.重複步驟2和3...
三種經典的洗牌演算法
主要有3中經典的洗牌演算法 1.抽牌 1.初始化原始陣列和新陣列,原始陣列長度為n 已知 2.從還沒處理的陣列 假如還剩k個 中,隨機產生乙個 0,k 之間的數字p 假設陣列從0開始 3.從剩下的k個數中把第p個數取出 4.重複步驟2和3直到數字全部取完 5.從步驟3取出的數字序列便是乙個打亂了的數...