題目:有一副鬥地主的撲克牌,設計乙個洗牌演算法。
問題很簡單,但問題的關鍵在於要設計乙個公平的演算法,使得每乙個位置都能等概率地放置每個元素。
由於之前沒有研究過這個問題,面試被問到就很尷尬,所以這裡記錄一下優秀的洗牌演算法。
乙個最簡單的方法
有n張牌,每次從中任意的取出一張
還剩下n-1張牌,然後從這n-1張牌中隨機選擇一張
。。。直至全部去完
這個演算法是公平的,簡單證明一下。
證明
有n張牌,那麼第乙個位置上的牌可以選擇n張,那麼也就是每張牌在第乙個位置的概率是1
n\frac
n1
現在剩餘n-1張牌,在這n-1張牌中選擇一張放在第二個位置的概率是1n−
1\frac
n−11
,但是實際概率應該還要乘在第一次中沒有被選中的概率,也就是1n−
1∗n−
1n=1
n\frac*\frac=\frac
n−11∗
nn−1
=n1
現在剩餘n-2張牌,在這n-2張牌中選擇一張放在第三個位置的概率是1n−
2\frac
n−21
,但是實際概率應該還要乘在第一次中沒有被選中的概率和第二次沒有被選中的概率,也就是n−1
n∗n−
2n−1
∗1n−
2=1n
\frac*\frac*\frac=\frac
nn−1∗
n−1n
−2∗
n−21
=n1
。。。剩下的一次遞推,結果都是一樣的。
但是這個演算法有個致命的缺陷就是時間複雜度太高,因為在具體的**實現中,你是需要進行乙個刪除操作的,這個複雜度是o(n
)o(n)
o(n)
的,也就是說這個樸素的演算法的複雜度是o(n
2)
o (n^2)
o(n2)。
優化演算法knuth-durstenfeld shhuffle
這個演算法的核心思想就是從最後開始,比如當前在位置i
ii,那麼就隨機乙個在i
ii之前的下標ind
ex
index
inde
x,交換ind
ex
index
index和i
ii的元素,然後向前推進一格。
for
(int i = n -
1; i >=
0; i --
)swap
(arr[i]
, arr[
rand()
%(i +1)
])
這個演算法也是公平的,但是將時間複雜度一下子縮小到了o(n
)o(n)
o(n)
證明
還是假設有n張牌,我們現在隨機乙個下標index,然後交換index和n兩個下標的牌,那麼隨機選擇的index可能是n張牌中的任何一張,那麼也就是說任何一張牌放在最後的位置n的概率是1
n\frac
n1確定了最後乙個位置的牌之後,現在確定第n-1個位置的牌,現在還剩下n-1個位置沒有牌,從n-1張牌隨機一張,選中的概率是1n−
1\frac
n−11
,將這張牌放在n-1的位置的概率還要乘上沒有在第一輪中被選中的概率,也就是1n−
1∗n−
1n=1
n\frac*\frac=\frac
n−11∗
nn−1
=n1
接下來同理,可以看到其實原理和前面的樸素演算法差不多,只是這個更加巧妙,但是也能證明這是乙個公平的演算法。
題目: 從n個元素中隨機等概率取出k個元素,n長度未知。
先說演算法,先選中第1到k個元素,作為被選中的元素。然後依次對第k+1至第n個元素做如下操作:
每個元素都有k
i\frac
ik的概率被選中,然後等概率的(也就是1
k\frac
k1)選擇乙個已經被選中的元素替換。其中i
ii是元素的序號。
證明
如果n<=k,那麼就很明顯直接選擇前n個即可,每個的概率都是1
如果n>k,那就先選擇前k個
接下來如果i=k
+1
i=k+1
i=k+
1,那麼第i個被選中的概率是kk+
1\frac
k+1k
,那麼對於前k個,都是預設被選中的,接下來每乙個都會有1
k\frac
k1的概率被替換,也就是有1−k
k+1∗
1k=1
−1k+
1=kk
+1
1-\frac*\frac=1-\frac=\frac
1−k+1k
∗k1
=1−
k+11
=k+
1k的概率保留,很明顯成立,每個元素都有相等的概率被選中。
如果j=i+1,那麼對於對於第j個元素,有ki+
1\frac
i+1k
的概率被選中,然後對於前i個元素,前面已經證明過了有k
i\frac
ik的概率被選中,現在只需要計算出保留的概率。
保留的概率=1- 被替換的概率=1−ki
+1∗1
k=1−
1i+1
=ii+
1-\frac*\frac=1-\frac=\frac
−i+1k
∗k1
=1−i
+11
=i+1
i所以前i個元素被選中的概率是ki∗
ii+1
=ki+
1\frac*\frac=\frac
ik∗i+
1i=
i+1k
以此類推,用數學歸納法可以證明演算法的正確性。
【演算法雜談4】神一樣的隨機演算法
三種洗牌演算法shuffle
蓄水池抽樣演算法 隨機洗牌演算法
蓄水池抽樣演算法隨機演算法的一種,用來從 n 個樣本中隨機選擇 k 個樣本,其中 n 非常大 以至於 n 個樣本不能同時放入記憶體 或者 n 是乙個未知數。其時間複雜度為 o n 包含下列步驟 假設有一維陣列 s,長度未知,需要從中隨機選擇 k 個元素,陣列下標從 1 開始 偽 如下 init a ...
演算法 蓄水池抽樣
例題 有乙個機器按自然數序列的方式吐出球,1號球,2號球.現有乙個袋子,袋子裡最多只能裝下k個球,並且除袋子以外沒有更多的空間,球扔掉不能放回。設計一種選擇方式,使得當機器吐出第n號球時,袋子中的球數是k個,同時可以保證從1號球到n號球中的每乙個被選中進袋子的概率都是k n。具體例子 有乙個只能裝下...
蓄水池抽樣演算法
給你乙個長度為n的鍊錶。n很大,但你不知道n有多大。你的任務是從這n個元素中隨機取出k個元素。你只能遍歷這個鍊錶一次。你的演算法必須保證取出的元素恰好有k個,且它們是完全隨機的 出現概率均等 蓄水池抽樣演算法 該演算法是針對從乙個序列中隨機抽取不重複的k個數,保證每個數被抽取到的概率為k n這個問題...