隨機題

orchid發表於2014-11-01

--------------------------------------------------------------

程式的輸入為兩個整數m和n,m<n。要輸出0~n-1範圍內的m個隨機整數的有序列表,m個整數不允許重複。這個題目來自<程式設計珠璣>。

 

解法1:

因為要求序列有序,那麼我們可以依序對0到n-1的每一個數做決策:輸出還是不輸出?很顯然每個數字輸出的概率應該是m/n才對。用Ai表示第i個數字被輸出,需要注意的是,A0,A1,A2...之間不是獨立的,比如P(A1)=(m-1/n-1)P(A0)+(m/n-1)(1-P(A0)),也就是對於0有沒有被輸出,輸出1的概率是不同的。

下面是C++程式碼,可以仔細琢磨一下

 

--------------------------------------------------------------

1. 已知有個rand7()的函式,返回1到7隨機自然數,怎樣利用這個rand7()構造rand10(),隨機1~10。

 

2. 已知有個randM()的函式,返回1到M隨機自然數,怎樣利用這個randM()構造randN(),隨機1~N。

上題的擴充套件。

當N<=M時可以直接得到。

當N>M時,類似構造(randM()-1)*M + randM(),可以產生1~M^2(即randM^2),可以在M^2中選出N個構造1~N的對映。

如果M^2還是沒有N大,則可以對於randM^2繼續構造,直到成功為止。

3. 已知一隨機發生器,產生0的概率是p,產生1的概率是1-p,現在要你構造一個發生器,使得它產生0和1的概率均為1/2。

考慮連續產生兩個隨機數,結果只有四種可能:00、01、10、11,其中產生01和產生10的概率是相等的,均為p*(1-p),於是可以利用這個概率相等的特性等概率地產生01隨機數。

比如把01對映為0,10對映為1。於是整個方案就是:

產生兩個隨機數,如果結果是00或11就丟棄重來,如果結果是01則產生0,結果是10則產生1。

4. 已知一隨機發生器,產生的數字的分佈不清楚,現在要你構造一個發生器,使得它產生0和1的概率均為1/2。

思路類似,考慮連續產生兩個隨機數a、b,結果有三種情況a==b,a>b,a<b,其中由於a和b的對稱性,a>b和a<b出現的概率是相等的,於是可以利用這個概率相等的特性等概率地產生01隨機數。方法類似。

或者可以找到另一種概率相等的事件,比如選擇一個閾值th,把隨機數的結果分為小於閾值和大於等於閾值兩種情況,於是連續產生兩個隨機數,他們一個小於閾值,另一個大於等於閾值的概率是相等。然後類似產生隨機數。

5. 已知一隨機發生器,產生0的概率是p,產生1的概率是1-p,構造一個發生器,使得它構造1、2、3的概率均為1/3;…。更一般地,構造一個發生器,使得它構造1、2、3、…n的概率均為1/n。

此時我們已經知道,要從n個數中等概率地產生一個隨機數,關鍵是要找到n個或更多個出現概率相等的事件,然後我們重複隨機地產生事件,如果是跟這n個事件不同的事件直接忽略,直到產生這n個事件中的一個,然後就產生跟這個事件匹配的隨機數。由於n個事件發生的概率相等,於是產生的隨機數的概率也是相等的。

考慮連續產生x個隨機數,結果應該是x個0跟1的組合,為了使某些結果出現的概率相等,我們應該要讓這個結果中0和1出現的次數相等,即各佔一半。於是x的長度必須是偶數的,為了方便,考慮連續產生2x個隨機數。每個0跟1各出現一半的結果可以賦予1到n的某個數,為了能夠表示這n個數,需要0跟1各出現一半的總結果數大於等於n,即

C(2*x, x) >= n

解出最小的x即為效率最高的x。

然後把前n個0和1個出現一半的結果分別賦予1到n的值。隨機時連續產生2*x個數,如果不是這n個結果中的一個則重新隨機,如果是的話則產生對應的值作為隨機結果。

6. 給出從n個數中隨機選擇m個數的方法。n很大,可以認為是億級別。m可以很小,如接近1;也可以很大,如接近n。

一個直接的思路是一直重複地隨機,直到隨機到m個數為止。這個方法有兩個弊端:

  1. 難以直到後面隨機到的一個數是否在前面已經隨機過了,因為資料量很大,無法儲存在記憶體中,如果儲存到外存中則時間花費太大。
  2. 如果m很大,甚至接近於n,則後面隨機到的數字基本上都是前面隨機過的,因而需要嘗試的隨機次數太多。

一個思路是每個數被選中的概率是m/n,則可以遍歷一遍原資料,在遍歷每個數字的同時以m/n的概率決定是否要選擇當前數字,則當遍歷完畢的時候,選擇到的數字在平均意義就是m個。這個會隨著n的增大而更好地趨近於m,但不能很精確地保證隨機到的數字一定是m個。

以上思路雖然不能滿足要求,但我們可以進行改進。剛才我們在遍歷每個數字的時候都是以同樣的概率m/n決定是否要選擇該數字,實際上,在當前遍歷數字的前面的數字的結果我們是已經知道了,我們可以根據前面的隨機結果動態地調整當前的隨機策略,使得最終能夠保證隨機到的數字一定是m個。

具體的做法是,遍歷第1個數字時有m/n的概率進行選擇,如果選擇了第1個數字,則第2個數字被選擇的概率調整為(m-1)/(n-1),如果沒選擇第1個數字,則第2個數字被選擇的概率為m/(n-1)。即遍歷到第i個數字的時候,如果此時已經選擇了k個,則以(m-k)/(n-i+1)的概率決定是否要選擇當前的第i個數字。

這樣可以保證每次都能夠保證在剩下的數字中能選擇適當的數使得總體選擇的數字是m個。比如,如果前面已經隨機了m個,則後面隨機的概率就變為0。如果前面一直都沒隨機到數字,則後面隨機到的概率就會接近1。最終得到的結果始終精確地是m個數字。

7. 給出從n個數中隨機選擇1個的方法。注意,n非常大,並且一開始不知道其具體值。數字是一個一個給你的,當給完之後,你必須立刻給出隨機的結果。

這裡n的值非常大,而且要求立即給出答案,所以不能把所有的數字先儲存起來,然後再慢慢考慮要隨機哪個。

這題跟上面一題比較類似,因為我們不知道數字到底有多少個,所以必須在得到每一個數字的時候就有一個當前的結果,這樣在數字給完的時候可以給出答案。

於是第1個數字是必須要拿的。問題是當第2個數字來的時候,究竟要保留手上的數字,還是拿當前的第2個數字呢?更一般地,當第i(i>1)個數字來的時候,究竟是保留手上的數字,還是選擇當前的第i個數字呢?

答案是要保證每個數字被選取的概率是相等,當第i個數來的時候,如果我們已經保證了前i-1個數每個數被選取的概率都是相等的,那麼只要第i個數字被選取的概率是1/i,我們就可以知道所有i個數被選取的概率都是1/i了。所以只需要以1/i的概率決定是否要選取當前的第i個數字即可。

於是可以保證對於任意的n,當給完n個數字時,選擇每個數字的概率都是相等的,為1/n。

8. 給出從n個數中隨機選擇m個的方法。注意,n非常大,並且一開始不知道其具體值。數字是一個一個給你的,當給完之後,你必須立刻給出隨機的結果。

這題是上一題的推廣,於是可以仿照著進行。

首先前m個數字是必須拿的。問題是當第i(i>m)個數字來的時候,究竟是要丟棄這個數,還是保留這個數?如果要保留這個數的話,則必須得丟棄手中已有的m個數,那是怎麼確定丟棄哪個呢?

下面是就具體的做法。第i個數到來的時候,以m/i的概率決定是否要選擇這個數字。如果選擇了這個數字,則隨機地替換掉手上m個數字中的一個。

如果前i-1個數字的時候保證了每個數字被選取的概率相等,則這樣做之後可以保證每個數字被選取的概率也相等,為m/i。

  1. 第i個數選擇的概率是m/i,因為演算法就是這樣決定的。
  2. 考慮前i-1個數字中的任意一個,它在第i個數之前被選擇的概率是m/(i-1)。在第i個數字的時候,這個數字要被選擇的話又兩種可能,一是第i個數沒有被選中(概率是1-m/i),二是第i個數倍選中了(概率是m/i)但是替換掉的數字不是它(概率是1-1/m),於是這個數在第i個數時仍然被選擇的概率是m/(i-1) * ((1-m/i) + (m/i * (1-1/m))) = m / (i-1) * ((i-1) / i) = m/i。

由數學歸納法原理知,對於任意的n,當給完n個數的時候,選擇的結果可以保證這n個數中每個被選中的概率都是相等的,為m/n。

 

Sulutions:

1,

產生隨機數的主要原則是每個數出現的概率是相等的,如果可以得到一組等概率出現的數字,那麼就可以從中找到對映為1~10的方法。

rand7()返回1~7的自然數,構造新的函式 (rand7()-1)*7 + rand7(),這個函式會隨機產生1~49的自然數。原因是1~49中的每個數只有唯一的第一個rand7()的值和第二個rand7()的值表示,於是它們出現的概率是相等。

但是這裡的數字太多,可以丟棄41~49的數字,把1~40的數字分成10組,每組對映成1~10中的一個,於是可以得到隨機的結果。

具體方法是,利用(rand7()-1)*7 + rand7()產生隨機數x,如果大於40則繼續隨機直到小於等於40為止,如果小於等於40,則產生的隨機數為(x-1)/4+1。

相關文章