隨機演算法全面總結

來源:互聯網
上載者:User

隨機演算法涉及大量機率論知識,有時候難得去仔細看推導過程,當然能夠完全瞭解推導的過程自然是有好處的,如果不瞭解推導過程,至少記住結論也是必要的。本文總結最常見的一些隨機演算法的題目,也當作面試的準備工作吧。需要說明的是,這裡用到的隨機函數都假定它能隨機的產生範圍[a,b]內的整數,即產生每個整數的機率相等。(雖然在實際中並不一定能實現,不過,誰在乎呢?這個世界都是這麼隨機)


一、隨機排列數組

假設給定一個數組A,它包含元素1到N,我們的目標是構造這個數組的一個隨機排列。

一個常用的方法是為數組每個元素A[i]賦一個隨機的優先順序P[i],然後依據優先順序對數組進行排序。比如我們的數組為A={1, 2, 3, 4},如果選擇的優先順序數組為P={36, 3, 97, 19},那麼就可以得到數列B={2, 4, 1, 3},因為3的優先順序最高(為97),而2的優先順序最低(為3)。這個演算法需要產生優先順序數組,還需使用優先順序數組對原數組排序,這裡就不詳細描述了,還有一種更好的方法可以得到隨機排列數組。

產生隨機排列數組的一個更好的方法是原地排列給定數組(in-place),可以在O(N)的時間內完成。虛擬碼如下:

RANDOMIZE-IN-PLACE ( A , n )     for i ←1 to n          do swap A[i] A[RANDOM(i , n )]

如代碼中所示,第i次迭代時,元素A[i]是從元素A[i]到A[n]中隨機選取的,在第i次迭代後,我們就再也不會改變A[i]。

A[i]位於任意位置j的機率為1/n。這個是很容易推導的,比如A[1]位於位置1的機率為1/n,這個顯然,因為A[1]不被1到n的元素替換的機率為1/n,而後就不會再改變A[i]了。而A[1]位於位置2的機率也是1/n,因為A[1]要想位於位置2,則必須在第一次與A[k]交換(k=2...n),同時第二次A[2]與A[k]替換,第一次與A[k]交換的機率為(n-1)/n,而第二次替換機率為1/(n-1),所以總的機率是(n-1)/n * 1/(n-1)
= 1/n。同理可以推導其他情況。

當然這個條件只能是隨機排列數組的一個必要條件,也就是說,滿足元素A[i]位於位置j的機率為1/n不一定就能說明這可以產生隨機排列數組。因為它可能產生的排列數目少於n!,儘管機率相等,但是排列數目沒有達到要求,演算法導論上面有一個這樣的反例。

演算法RANDOMIZE-IN-PLACE可以產生均勻隨機排列,它的證明過程如下:

首先給出k排列的概念,所謂k排列就是從n個元素中選取k個元素的排列,那麼它一共有n!/(n-k)!個k排列。

迴圈不變式:for迴圈第i次迭代前,對於每個可能的i-1排列,子數組A[1...i-1]包含該i-1排列的機率為(n-i+1)! / n!。

初始化:在第一次迭代前,i=1,則迴圈不變式指的是對於每個0排列,子數組A[1...i-1]包含該0排列的機率為(n-1+1)! / n! = 1。A[1...0]為空白的數組,0排列則沒有任何元素,因此A包含所有可能的0排列的機率為1。不變式成立。

維持:假設在第i次迭代前,數組的i-1排列出現在A[1...i-1]的機率為(n-i+1) !/ n!,那麼在第i次迭代後,數組的所有i排列出現在A[1...i]的機率為(n-i)! / n!。下面來推導這個結論:

考慮一個特殊的i排列p = {x1, x2, ... xi},它由一個i-1排列p' ={x1, x2,..., xi−1}後面跟一個xi構成。設定兩個事件變數E1和E2:

E1為該演算法將排列p‘放置到A[1...i-1]的事件,機率由歸納假設得知為Pr(E1) = (n-i+1)! / n!。

E2為在第i次迭代時將xi放入到A[i]的事件。

因此我們得到i排列出現在A[1...i]的機率為Pr {E2 ∩ E1} = Pr {E2 | E1} Pr {E1}.而Pr {E2 | E1} = 1/(n − i + 1),所以

Pr {E2 ∩ E1} = Pr {E2 | E1} Pr {E1}= 1 /(n − i + 1) * (n − i + 1)! / n! = (n − i )! / n!。

結束:結束的時候i=n+1,因此可以得到A[1...n]是一個給定n排列的機率為1/n!。

擴充題

如果上面的隨機排列演算法寫成下面這樣,是否也能產生均勻隨機排列?

PERMUTE-WITH-ALL( A , n )     for i ←1 to n          do swap A[i] A[RANDOM(1 , n )]

注意,該演算法不能產生均勻隨機排列。假定n=3,則該演算法可以產生3*3*3=27個輸出,而3個元素只有3!=6個不同的排列,要使得這些排列出現機率等於1/6,則必須使得每個排列出現次數m滿足m/27=1/6,顯然,沒有這樣的整數符合條件。而實際上各個排列出現的機率如下,如{1,2,3}出現的機率為4/27,不等於1/6。


二、隨機選取一個數字

題目:給定一個未知長度的整數流,如何隨機選取一個數?(所謂隨機就是保證每個數被選取的機率相等)

解法1:

如果資料流不是很長,可以存在數組中,然後再從數組中隨機選取。當然題目說的是未知長度,所以如果長度很大不足以儲存在記憶體中的話會很麻煩。這種解法有其局限性。

解法2:

如果資料流在第1個數字後結束,那麼必選第1個數字。

如果資料流在第2個數字後結束,那麼我們選第2個數位機率為1/2,我們以1/2的機率用第2個數字替換前面選的隨機數,得到新的隨機數。

.........

如果資料流在第n個數字後結束,那麼我們選擇第n個數位機率為1/n,即我們以1/n的機率用第n個數字替換前面選的隨機數,得到新的隨機數。

一個簡單的方法就是使用隨機函數f(n)=bigrand()%n,其中bigrand()返回很大的隨機整數,當資料流到第n個數時,如果f(n)==0,則替換前面的已經選的隨機數,這樣可以保證每個數字被選中的機率都是1/n。如當n=1時,則f(1)=0,則選擇第1個數,當n=2時,則第2個數被選中的機率為1/2,以此類推,當數字長度為n時,第n個數字被選中的機率為1/n。


三、隨機選取M個數字 題目:程式輸入包含兩個整數m和n,其中m<n,輸出是0~n-1範圍內的m個隨機整數的有序列表,不允許重複。從機率角度來說,我們希望得到沒有重複的有序選擇,其中每個選擇出現的機率相等。
解法1:先考慮個簡單的例子,當m=2,n=5時,我們需要從0~4這5個整數中等機率的選取2個有序的整數,且不能重複。如果採用如下條件選取:bigrand() % 5 < 2,則我們選取0的機率為2/5。但是我們不能採取同樣的機率來選取1,因為選取了0後,我們應該以1/4的機率來選取1,而在沒有選取0的情況下,我們應該以2/4的機率選取1。選取的虛擬碼如下:
select = mremaining = nfor i = [0, n)    if (bigrand() % remaining < select)         print i         select--    remaining--
只要滿足條件m<=n,則程式輸出m個有序整數,不多不少。不會多選,因為每選擇一個數,select--,這樣當select減到0後就不會再選了。同時,也不會少選,因為每次都會remaining--,當select/remaining=1時,一定會選取一個數。每個子集被選擇的機率是相等的,比如這裡5選2則共有C(5,2)=10個子集,如{0,1},{0,2}...等,每個子集被選中的機率都是1/10。更一般的推導,n選m的子集數目一共有C(n,m)個,考慮一個特定的m序列,如0...m-1,則選取它的機率為m/n
* (m-1)/(n-1)*....1/(n-m+1)=1/C(n,m),可以看到機率是相等的。C++語言實現代碼如下,該演算法時間複雜度為O(n)。
void genknuth(int m, int n){         for (int i=0; i<n; i++)             if (bigrand() % (n-i) < m) {  //n-i中i每次加1,相當於remaining每次減1                 cout << i << endl;                 m--;  //選取的數目減1             }}

解法2:在初始為空白的集合中插入隨機整數,直到數目達到m。由於每次插入操作需要O(logm)的時間,遍曆集合需要O(m)的時間,該演算法總共需要O(mlogm)的實際。代碼:

void gensets(int m, int n){     set<int> S;     while (S.size() < m)           S.insert(bigrand() % n);     set<int>::iterator it;     for (it = S.begin(); it != S.end(); it++)          cout << *it << endl;}

解法3:採用前面隨機排列數組的思想,先對前m個數字進行隨機排列,然後排序這m個數字並輸出即可。代碼省略。

四、rand7()產生rand10()問題請參見http://blog.csdn.net/ssjhust123/article/details/7753012

五、趣味機率題 1) 生男生女問題:在重男輕女的國家裡,男女的比例是多少?在一個重男輕女的國家裡,每個家庭都想生男孩,如果他們生的孩子是女孩,就再生一個,直到生下的是男孩為止。這樣的國家,男女比例會是多少?

答案:還是1:1。在所有出生的第一個小孩中,男女比例是1:1;在所有出生的第二個小孩中,男女比例是1:1;.... 在所有出生的第n個小孩中,男女比例還是1:1。所以總的男女比例是1:1。

 

2)約會問題:兩人相約5點到6點在某地會面,先到者等20分鐘後離去,求這兩人能夠會面的機率。

答案:設兩人分別在5點X分和5點Y分到達目的地,則他們能夠會面的條件是|X-Y| <= 20,而整個範圍為S={(x, y): 0 =< x <= 60,  0=< y <= 60},所以會面的情況為圖中表示的面積,機率為(60^2 - 40^2) / 60^2 = 5/9。

3)帽子問題:有n位顧客,他們每個人給餐廳的服務生一頂帽子,服務生以隨機的順序歸還給顧客,請問拿到自己帽子的顧客的期望數是多少?

答案:使用指示隨機變數來求解這個問題會簡單些。定義一個隨機變數X等於能夠拿到自己帽子的顧客數目,我們要計算的是E[X]。對於i=1, 2 ... n,定義Xi =I {顧客i拿到自己的帽子},則X=X1+X2+...Xn。由于歸還帽子的順序是隨機的,所以每個顧客拿到自己帽子的機率為1/n,即Pr(Xi=1)=1/n,從而E(Xi)=1/n,所以E(X)=E(X1+X2+...Xn)=E(X1)+E(X2)+...E(Xn)=n*1/n
= 1。即大約有1個顧客可以拿到自己的帽子。

4)生日悖論:
一個房間至少要有多少人,才能使得有兩個人的生日在同一天?
答案:對房間k個人中的每一對(i, j)定義指標變數Xij = {i與j生日在同一天} ,則i與j生日相同時,Xij=1,否則Xij=0。兩個人在同一天生日的機率Pr(Xij=1)=1/n。則用X表示同一天生日的兩人對的數目,則E(X)=E(∑ki=1 ∑kj=i+1 Xij) = C(k,2)*1/n = k(k-1)/2n,令k(k-1)/2n >=1, 可得到k>=28,即至少要有28個人,才能期望兩個人的生日在同一天。


5)如果在高速公路上30分鐘內看到一輛車開過的幾率是0.95,那麼在10分鐘內看到一輛車開過的幾率是多少?(假設常機率條件下)

答案:假設10分鐘內看到一輛車開過的機率是x,那麼沒有看到車開過的機率就是1-x,30分鐘沒有看到車開過的機率是(1-x)^3,也就是0.05。所以得到方程(1-x)^3 = 0.05 ,解方程得到x大約是0.63。 

 

參考資料

《演算法導論》

《編程珠璣2》

http://blog.csdn.net/wxwtj/article/details/6621430





聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.