隨機(jī)洗牌算法整理

在游戲里面有各種“隨機(jī)”的需求妥凳,比如從n個用戶里面隨機(jī)給m個發(fā)獎勵。那么码泛,要如何實現(xiàn)呢猾封?簡單的來說,可以調(diào)一個stl的函數(shù)來實現(xiàn)噪珊,偽代碼如下:

vector<int> foo(vector<int> & player, int m)
{
  std::random_shuffle(players.begin(), players.end());

  vector<int> result;
  for (size_t i = 0; i < m; i++)
  {
      result.push_back(players[i]);
  }
  return result;
}

那shuffle背后是怎么實現(xiàn)的呢晌缘?或者說如何設(shè)計高效并且保證隨機(jī)概率是相同的洗牌算法呢?

1. 不帶權(quán)重的隨機(jī)洗牌算法
  • 最笨的方法
    從數(shù)組里面隨機(jī)拿出兩個元素痢站,進(jìn)行交換磷箕。完成一次洗牌。不停的重復(fù)以上過程阵难。重復(fù)k次岳枷。當(dāng)k足夠大的時候,就洗均勻了呜叫。這個方法沒什么好說的空繁,除了簡單以外就全是缺點了。

  • Yates洗牌
    這個方法是一個很古老的方法朱庆,非常好理解盛泡。思路是是每次從數(shù)組里面隨機(jī)抽一個元素,放到另外的結(jié)果集中娱颊。然后不停的重復(fù)上述過程傲诵,直到抽出m個凯砍。但是,由于這個方法要每次對數(shù)組的元素進(jìn)行刪除拴竹,所以悟衩,時間復(fù)雜度為O(m*n) 。

  • knuth洗牌 (高德納洗牌)

算法的思路是栓拜,從頭開始遍歷數(shù)組座泳,對于下標(biāo)是i的元素,從i到n之間隨機(jī)出一個下標(biāo)菱属,對這個元素進(jìn)行交換钳榨。

偽代碼的流程是:

for i in (0, n) 
    從i 到 n隨機(jī)出一個數(shù)j
    交換a[j] 和 a[i]

知乎上有個很有意思的問題有哪些驚艷你的算法?纽门,也提到了這個洗牌算法薛耻。

有哪些算法驚艷到了你? - 劉宇波的回答 - 知乎
https://www.zhihu.com/question/26934313/answer/743798587


小結(jié):
knuth算法保留了原數(shù)組的所有元素赏陵,僅改變了元素的順序饼齿,時間復(fù)雜度為O(m)。


2. 帶權(quán)重的隨機(jī)洗牌算法

上述討論的問題是“如何公平的從長度是n的數(shù)組里面隨機(jī)出m個”蝙搔,這里的假設(shè)是每個元素被隨機(jī)出來的概率是相同的缕溉。

那問題再復(fù)雜些,希望實現(xiàn)“非公平抽獎”吃型,比如:一個集合里有 n 個元素证鸥,每個元素有不同的權(quán)重,現(xiàn)在要不放回地隨機(jī)抽取 m 個元素勤晚,每個元素被抽中的概率為元素的權(quán)重占總權(quán)重的比例枉层。

  • 樸素帶權(quán)重抽樣方法

簡化下問題模式,假設(shè)權(quán)重的和為1赐写,只抽一個人獲獎m=1鸟蜡。比如 :a,b,c,d 的權(quán)重分別是 0.1,0.3挺邀,0.2揉忘,0.4,從里面選一個中獎端铛。

那這個就很簡單了泣矛,從0~1之間隨機(jī)一個數(shù),這個數(shù)落在誰的區(qū)間上禾蚕,就算誰中獎乳蓄。

|-0.1-|---0.3---|--0.2--|----0.4----|
   ^       ^        ^         ^
   a       b        c         d

數(shù)學(xué)的原理很簡單,可以理解為 上面的線段上隨機(jī)出一個數(shù)字夕膀,誰占的長度多(概率大就是長度多)虚倒,那么誰中獎的概率就大。

剛才是問題的簡化版本 m=1(只有一個人中獎)产舞,如果希望是m=2的話魂奥,就重復(fù)上述過程m次。當(dāng)有人抽到了易猫,就把它移除出來耻煤,然后再重復(fù)。

# m=1只有一個人抽獎的情況
# weight_data 的數(shù)據(jù)類型為 dict
def random_weight(weight_data):
    total = sum(weight_data.values())    # 權(quán)重求和
    ra = random.uniform(0, total)   # 在0與權(quán)重和之前獲取一個隨機(jī)數(shù)
    curr_sum = 0
    ret = None
    keys = weight_data.keys()        # 使用Python3.x中的keys
    for k in keys:
        curr_sum += weight_data[k]             # 在遍歷中准颓,累加當(dāng)前權(quán)重值
        if ra <= curr_sum:          # 當(dāng)隨機(jī)數(shù)<=當(dāng)前權(quán)重和時哈蝇,返回權(quán)重key
            ret = k
            break
    return ret
# 當(dāng)需要 m > 1的時
def foo(data, N):
    results = []
    i = 0
    while i < N:
        m = random_weight(data)   # 重復(fù)n次
        if m in results:
            continue
        results.append(m)
        i = i+1
    return results

小結(jié):
如果要選取 m 個元素,則可以按上面的方法先選取一個攘已,將該元素從集合中去除炮赦,再反復(fù)按上面的方法抽取剩余的元素。這種方法的復(fù)雜度是 O(mn)样勃,并且將元素從集合中刪除其實不太方便實現(xiàn)吠勘。

對于帶權(quán)重抽樣,有兩個更好的方法來解決樸素帶權(quán)重抽樣的一些弊端峡眶,具體的細(xì)節(jié)在這里:


參考:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末剧防,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子辫樱,更是在濱河造成了極大的恐慌峭拘,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件狮暑,死亡現(xiàn)場離奇詭異鸡挠,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)心例,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進(jìn)店門宵凌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人止后,你說我怎么就攤上這事瞎惫。” “怎么了译株?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵瓜喇,是天一觀的道長。 經(jīng)常有香客問我歉糜,道長乘寒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任匪补,我火速辦了婚禮伞辛,結(jié)果婚禮上烂翰,老公的妹妹穿的比我還像新娘。我一直安慰自己蚤氏,他們只是感情好甘耿,可當(dāng)我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著竿滨,像睡著了一般佳恬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上于游,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天毁葱,我揣著相機(jī)與錄音,去河邊找鬼贰剥。 笑死倾剿,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的鸠澈。 我是一名探鬼主播柱告,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼笑陈!你這毒婦竟也來了际度?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤涵妥,失蹤者是張志新(化名)和其女友劉穎乖菱,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蓬网,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡窒所,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了帆锋。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片吵取。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖锯厢,靈堂內(nèi)的尸體忽然破棺而出皮官,到底是詐尸還是另有隱情,我是刑警寧澤实辑,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布捺氢,位于F島的核電站,受9級特大地震影響剪撬,放射性物質(zhì)發(fā)生泄漏摄乒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望馍佑。 院中可真熱鬧斋否,春花似錦、人聲如沸挤茄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽穷劈。三九已至,卻和暖如春踊沸,著一層夾襖步出監(jiān)牢的瞬間歇终,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工逼龟, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留评凝,地道東北人。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓腺律,卻偏偏與公主長得像奕短,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子匀钧,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容