隨機題

--------------------------------------------------------------算法

程序的輸入爲兩個整數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。io

 

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、0一、十、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,構造一個發生器,使得它構造一、二、3的機率均爲1/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。

相關文章
相關標籤/搜索