數組應用【洗牌問題】

思考:

設計一個公平的洗牌算法

1.

      看問題,洗牌,顯然是一個隨機算法了。隨機算法還不簡單?隨機唄。把所有牌放到一個數組中,每次取兩張牌交換位置,隨機 k 次即可。如果你的答案是這樣,通常面試官會進一步問一下,k 應該取多少?100?1000?10000?很顯然,取一個固定的值不合理。如果數組中有 1000000 個元素,隨機 100 次太少;如果數組中只有 10 個元素,隨機 10000 次又太多。一個合理的選擇是,隨機次數和數組中元素大小相關。比如數組有多少個元素,我們就隨機多少次。這個答案已經好很多了。但其實,連這個問題的本質都沒有觸及到。此時,面試官一定會狡黠地一笑:這個算法公平嗎?

2、

我們再看問題:設計一個公平的洗牌算法。

問題來了,對於一個洗牌算法來說,什麼叫“公平”?這其實是這個問題的實質,我們必須定義清楚:什麼叫公平。

       一旦你開始思考這個問題,才觸及到了這個問題的核心。在我看來,不管你能不能最終給出正確的算法,如果你的思路是在思考對於洗牌算法來說,什麼是“公平”,我都覺得很優秀。因爲背出一個算法是簡單的,但是這種探求問題本源的思考角度,絕不是一日之功。別人告訴你再多次“要定義清楚問題的實質”都沒用。這是一種不斷面對問題,不斷解決問題,逐漸磨鍊出來的能力,短時間內無法培訓。這也是我經常說的,面試不是標準化考試,不一定要求你給出正確答案。面試的關鍵,是看每個人思考問題的能力。說回我們的洗牌算法,什麼叫公平呢?一旦你開始思考這個問題,其實答案不難想到。洗牌的結果是所有元素的一個排列。一副牌如果有 n 個元素,最終排列的可能性一共有 n! 個。公平的洗牌算法,應該能等概率地給出這 n! 個結果中的任意一個。如思考到這一點,我們就能設計出一個簡單的暴力算法了:對於 n 個元素,生成所有的 n! 個排列,然後,隨機抽一個。這個算法絕對是公平的。但問題是,複雜度太高。複雜度是多少呢?O(n!)。因爲,n 個元素一共有 n! 種排列,我們求出所有 n! 種排列,至少需要 n! 的時間。有一些同學對 O(n!) 沒有概念。我本科時就鬧過笑話,正兒八經地表示 O(n!) 並不是什麼大不了不起的複雜度。實際上,這是一個比指數級 O(2^n) 更高的複雜度。因爲 2^n 是 n 個 2 相乘;而 n! 也是 n 個數字相乘,但除了 1,其他所有數字都是大於等於 2 的。當 n>=4 開始,n! 以極快的的速度超越 2^n。O(2^n) 已經被稱爲指數爆炸了。O(n!) 不可想象。所以,這個算法確實是公平的,但是,時間不可容忍。

3.

       我們再換一個角度思考“公平”這個話題。其實,我們也可以認爲,公平是指,對於生成的排列,每一個元素都能等概率地出現在每一個位置。或者反過來,每一個位置都能等概率地放置每個元素。這個定義和上面的最終洗牌結果,可以等概率地給出這 n! 個排列中的任意一個,是等價的。這個等價性,可以證明出來。並不難。如果正在學習概率論的同學,還比較習慣概率論處理問題的思想,應該能很快搞定:)

基於這個定義,我們就可以給出一個簡單的算法了。說這個算法簡單,是因爲他的邏輯太容易了,就一個循環:

 

 

這麼簡單的一個算法,可以保證上面我所說的,對於生成的排列,每一個元素都能等概率的出現在每一個位置。或者反過來,每一個位置都能等概率的放置每個元素。大家可以先簡單的理解一下這個循環在做什麼。其實非常簡單,i 從後向前,每次隨機一個 [0...i] 之間的下標,然後將 arr[i] 和這個隨機的下標元素,也就是 arr[rand() % (i + 1)] 交換位置。大家注意,由於每次是隨機一個 [0...i] 之間的下標,所以,我們的計算方式是 rand() % (i + 1),要對 i + 1 取餘,保證隨機的索引在 [0...i] 之間。

這個算法就是大名鼎鼎的 Knuth-Shuffle,即 Knuth 洗牌算法。

這個算法的原理,我們稍後再講。先來看看 Knuth 何許人也?

中文名:高納德。算法理論的創始人。我們現在所使用的各種算法複雜度分析的符號,就是他發明的。上世紀 60-70 年代計算機算法的黃金時期,近乎就是他一手主導的。他的成就實在太多,有時間單獨發文介紹,但是,我覺得一篇文章是不夠的,一本書還差不多。大家最津津樂道的,就是他所寫的《The Art of Computer Programming》,簡稱 TAOCP。這套書準備寫七卷本,然後,到今天還沒有寫完,但已經被《科學美國人》評爲可以媲美相對論的鉅著。微軟是 IT 界老大的年代,比爾蓋茨直接說,如果你看完了這套書的第一卷本,請直接給我發簡歷。至於這套書爲什麼寫的這麼慢?因爲老爺子寫到一半,覺得當下的文字排版工具都太爛,於是轉而發明出了現在流行的LaTex文字排版系統...另外,老爺子可能覺得當下的編程語言都不能完美地表現自己的邏輯思想,還發明了一套抽象的邏輯語言,用於這套書中的邏輯表示...

4.

是時候仔細的看一下,這個簡單的算法,爲什麼能做到保證:對於生成的排列,每一個元素都能等概率的出現在每一個位置了。

在這裏,我們模擬一下算法的執行過程,同時,對於每一步,計算一下概率值。

我們簡單的只是用 5 個數字進行模擬。假設初始的時候,是按照 1,2,3,4,5 進行排列的。

那麼,根據這個算法,首先會在這五個元素中選一個元素,和最後一個元素 5 交換位置。假設隨機出了 2

下面,我們計算 2 出現在最後一個位置的概率是多少?非常簡單,因爲是從 5 個元素中選的嘛,就是 1/5。實際上,根據這一步,任意一個元素出現在最後一個位置的概率,都是 1/5。

下面,根據這個算法,我們就已經不用管 2 了,而是在前面 4 個元素中,隨機一個元素,放在倒數第二的位置。假設我們隨機的是 3。3 和現在倒數第二個位置的元素 4 交換位置。

下面的計算非常重要。3 出現在這個位置的概率是多少?計算方式是這樣的:

其實很簡單,因爲 3 逃出了第一輪的篩選,概率是 4/5,但是 3 沒有逃過這一輪的選擇。在這一輪,一共有4個元素,所以 3 被選中的概率是 1/4。因此,最終,3 出現在這個倒數第二的位置,概率是 4/5 * 1/4 = 1/5。還是 1/5 !

實際上,用這個方法計算,任意一個元素出現在這個倒數第二位置的概率,都是 1/5。

相信聰明的同學已經瞭解了。我們再進行下一步,在剩下的三個元素中隨機一個元素,放在中間的位置。假設我們隨機的是 1。

關鍵是:1 出現在這個位置的概率是多少?計算方式是這樣的:

即 1 首先在第一輪沒被選中,概率是 4/5,在第二輪又沒被選中,概率是 3/4 ,但是在第三輪被選中了,概率是 1/3。乘在一起,4/5 * 3/4 * 1/3 = 1/5。

用這個方法計算,任意一個元素出現在中間位置的概率,都是 1/5。

這個過程繼續,現在,我們只剩下兩個元素了,在剩下的兩個元素中,隨機選一個,比如是4。將4放到第二個位置。

然後,4 出現在這個位置的概率是多少?4 首先在第一輪沒被選中,概率是 4/5;在第二輪又沒被選中,概率是 3/4;第三輪還沒選中,概率是 2/3,但是在第四輪被選中了,概率是 1/2。乘在一起,4/5 * 3/4 * 2/3 * 1/2 = 1/5。

用這個方法計算,任意一個元素出現在第二個位置的概率,都是 1/5。

最後,就剩下元素5了。它只能在第一個位置待著了。

那麼 5 留在第一個位置的概率是多少?即在前 4 輪,5 都沒有選中的概率是多少?

在第一輪沒被選中,概率是 4/5;在第二輪又沒被選中,概率是 3/4;第三輪還沒選中,概率是 2/3,在第四輪依然沒有被選中,概率是 1/2。乘在一起,4/5 * 3/4 * 2/3 * 1/2 = 1/5。

你看,在整個過程中,每一個元素出現在每一個位置的概率,都是 1/5 !

所以,這個算法是公平的。

當然了,上面只是舉例子。這個證明可以很容易地拓展到數組元素個數爲 n 的任意數組。整個算法的複雜度是 O(n) 的。

轉自:https://www.cnblogs.com/zhanghongfeng/p/11216793.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章