洗牌的學問

2021-09-22 23:23:53 字數 2076 閱讀 4879

幾乎所有的程式設計師都寫過類似於「洗牌」的演算法,也就是將乙個陣列隨機打亂後輸出,雖然很簡單,但是深入研究起來,這個小小的演算法也是大有講究。我在面試程式設計師的時候,就會經常讓他們當場寫乙個洗牌的函式,從中可以觀察到他們對於這個問題的理解和寫程式的基本功。

在深入討論之前,必須先定義出乙個基本概念:究竟洗牌演算法的本質是什麼?也就是說,什麼樣的洗牌結果是「正確」的?

雲風曾經有一篇博文,專門討論了這個問題,他也給出了乙個比較確切的定義,在經過洗牌函式後,如果能夠保證每乙個資料出現在所有位置的概率是相等的,那麼這種演算法是符合要求的。在這個前提下,盡量降低時間複雜度和空間複雜度就能得到好的演算法。

第乙個洗牌演算法:

隨機抽出一張牌,檢查這張牌是否被抽取過,如果已經被抽取過,則重新抽取,直到找到沒被抽出過的牌,然後把這張牌放入洗好的佇列中,重複該過程,直到所有的牌被抽出。

大概是比較符合大腦對於洗牌的直觀思維,這個演算法經常出現在我遇到的面試結果中,雖然它符合我們對於洗牌演算法的基本要求,但這個演算法並不好,首先它的複雜度為o(n2),而且需要額外的記憶體空間儲存已經被抽出的牌的索引。所以當資料量比較大時,會極大降低效率。

第二個演算法:

設牌的張數為n,首先準備n個不容易碰撞的隨機數,然後進行排序,通過排序可以得到乙個打亂次序的序列,按照這個序列將牌打亂。

這也是乙個符合要求的演算法,但是同樣需要額外的儲存空間,在複雜度上也會取決於所採用的排序演算法,所以仍然不是乙個好的演算法。

第三個演算法:

每次隨機抽出兩張牌交換,重複交換一定次數次後結束

void

shuffle

(int

data

intlength)}

這又是乙個常見的洗牌方法,比較有意思的問題是其中的「交換次數」,我們該如何確定乙個合適的交換次數?簡單的計算,交換m次後,具體某張牌始終沒有被抽到的概率為((n-2)/n)^m,如果我們要求這個概率小於1/1000,那麼m>-3*ln(10)/ln(1-2/n),對於52張牌,這個數大約是176次,需要注意的是,這是滿足「具體某張牌」始終沒有被抽到的概率,如果需要滿足「任意一張牌」沒被抽到的概率小於1/1000,需要的次數還要大一些,但這個概率計算起來比較複雜,有興趣的朋友可以試一下。

update: 這個概率是

第四個演算法:

從第一張牌開始,將每張牌和隨機的一張牌進行交換

void

shuffle

(int

data

intlength)}

很明顯,這個演算法是符合我們先前的要求的,時間複雜度為o(n),而且也不需要額外的臨時空間,似乎我們找到了最優的演算法,然而事實並非如此,看下乙個演算法。

第五個演算法:

void

shuffle

(int

data

intlength)}

乙個有意思的情況出現了,這個演算法和第三種演算法非常相似,從直覺來說,似乎使資料「雜亂」的能力還要弱於第三種,但事實上,這種演算法要強於第三種。要想嚴格的證明這一點並不容易,需要一些數學功底,有興趣的朋友可以參照一下這篇**,或者matrix67大牛的博文,也可以這樣簡單理解一下,對於n張牌的資料,實際排列的可能情況為n! 種,但第四種演算法能夠產生n^n種排列,遠遠大於實際的排列情況,而且n^n不能被n!整除,所以經過演算法四所定義的牌與牌之間的交換程式,很可能一張牌被換來換去又被換回到原來的位置,所以這個演算法不是最優的。而演算法五輸出的可能組合恰好是n!種,所以這個演算法才是完美的。

事情並沒有結束,如果真的要找乙個最優的演算法,還是請出最終的冠軍吧!

第六個演算法:

void

shuffle

(int

data

intlength)

沒錯,用c++的標準庫函式才是最優方案,事實上,std::random_shuffle在實現上也是採取了第四種方法,看來還是那句話,「不要重複製造輪子」

洗牌的學問

2009年09月22日 幾乎所有的程式設計師都寫過類似於 洗牌 的演算法,也就是將乙個陣列隨機打亂後輸出,雖然很簡單,但是深入研究起來,這個小小的演算法也是大有講究。我在面試程式設計師的時候,就會經常讓他們當場寫乙個洗牌的函式,從中可以觀察到他們對於這個問題的理解和寫程式的基本功。在深入討論之前,必...

完美洗牌 洗牌

完美洗牌問題,給定乙個陣列a1,a2,a3,an,b1,b2,b3.bn,把它最終設定為b1,a1,b2,a2,bn,an這樣的。o n 的演算法,o n 的空間。對於前n個數,對映為f i 2 i 1,0 i n 2 比如0 1,1 3 對於後n個數,對映為f i 2 i n 2 n 2 i n ...

怪異的洗牌

題目描述 對於一副撲克牌,我們有多種不同的洗牌方式。一種方法是從中間某個位置分成兩半,然後相交換,我們稱之為移位 shift 比如原來的次序是123456,從第4個位置交換,結果就是561234。這個方式其實就是陣列的迴圈移位,為了多次進行這個操作,必須使用一種盡可能快的方法來程式設計實現。在本題目...