咱們先來看這樣一個問題, 已知rand5能等機率產生1, 2, 3, 4, 5, 現要用rand5來實現rand7(rand7的意思是要等機率產生1, 2, 3, 4, 5, 6, 7), 該怎麼搞呢? 我看了一下網上資料, 不少都是湊出來一個結果, 沒有什麼過程思路, 我以爲雖然結果正確, 但總感受所用的技巧性太強。 因此, 在文本中, 我也來湊湊熱鬧, 看看該如何下手, 並給出程序的實際驗證結果。數組
咱們看看rand5 + rand5 行不行。 rand5 + rand5 的結果是2, 3, 4, 5, 6, 7, 8, 9, 10, 稍微思考一下, 就知道, 這些數確定不是等機率的, 好比2的機率要低於5的機率。 因此, 不靠譜。 咱們再來看看, 既然有了1, 2, 3, 4, 5, 那很容易就有10, 20, 30, 40, 50, 且是等機率的。 假設如今又有另一個fun函數, 能等機率隨機生成0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 那麼, 咱們不就很輕易地構造了等機率的10, 11, 12, 13, ....., 59麼? 沒錯, 思路就是這樣的。dom
因此, 咱們先要讓rand5產生等機率的間距數組(好比上述的10, 20, 30, 40, 50,), 而後讓rand5產生連續的待插入數字(好比上述的0, 1, 2, ..., 9,). 如今問題是, 要多大的間距才合適呢? 其實也很簡單, 要讓0, 1, 2, 3, 4恰好能插入到間距數組中。函數
到這裏, 就比較俗套了:spa
第一步: 用rand5產生等機率的0, 1, 2, 3, 4,準備插入到下一步的等間距數組中, 使得插入後, 恰好合適。.net
第二步: 用rand5產生等機率的0, 1, 2, 3, 4, 而後爲了被插入, 將其散開成0, 5, 10, 15, 20.遞歸
第三步: 將第一步插入 到第二步中, 因而, 就造成了0, 1, 2, 3, 4, 5, 6, 7, 8, ..., 20, 21, 22, 23, 24. 而後就很容易等機率地生成1, 2, 3, 4, 5, 6, 7了。get
題目:table
給定一個函數rand5(),該函數能夠隨機生成1-5的整數,且生成機率同樣。現要求使用該函數構造函數rand7(),使函數rand7()能夠隨機等機率的生成1-7的整數。模板
思路:class
不少人的第一反應是利用rand5() + rand()%3來實現rand7()函數,這個方法確實能夠產生1-7之間的隨機數,可是仔細想一想能夠發現數字生成的機率是不相等的。rand()%3 產生0的機率是1/5,而產生1和2的機率都是2/5,因此這個方法產生6和7的機率大於產生5的機率。
正確的方法是利用rand5()函數生成1-25之間的數字,而後將其中的1-21映射成1-7,丟棄22-25。例如生成(1,1),(1,2),(1,3),則當作rand7()中的1,若是出現剩下的4種,則丟棄從新生成。
簡單實現:
Java代碼
rand5() 它可以等機率生成 1-5 之間的整數。所謂等機率就是1,2,3,4,5 生產的機率均爲 0.2 。如今利用rand5(), 構造一個可以等機率生成 1- 7 的方法。這裏有兩個特別重要的點,一是 若是 rand5() + rand5(), 咱們可以產生一個均勻分佈的 1 - 10 嗎? 答案是否認的。好比對於 6來說(4+2, 2+4, 3+3),它被生成的生成的機率比1 (1+0,0+1)要大。
第二個點就是咱們不可能用rand5()直接產生 1- 7 的數,無論你用加減乘除都不行。因此,咱們要構造一個更大的範圍,使得範圍裏每個值被生成的機率是同樣的,並且這個範圍是7的倍數。
先產生一個均勻分佈的 0, 5, 10, 15, 20的數,再產生一個均勻分佈的 0, 1, 2, 3, 4 的數。相加之後,會產生一個 0到24的數,並且每一個數(除0外)生成的機率是同樣的。咱們只取 1 - 21 這一段,和7 取餘之後+1就能獲得徹底均勻分佈的1-7的隨機數了。
個人備註:
這種思想是基於,rand()產生[0,N-1],把rand()視爲N進制的一位數產生器,那麼可使用rand()*N+rand()來產生2位的N進制數,以此類推,能夠產生3位,4位,5位...的N進制數。這種按構造N進制數的方式生成的隨機數,一定能保證隨機,而相反,藉助其餘方式來使用rand()產生隨機數(如 rand5() + rand()%3 )都是不能保證機率平均的。
此題中N爲5,所以可使用rand5()*5+rand5()來產生2位的5進制數,範圍就是1到25。再去掉22-25,剩餘的除3,以此做爲rand7()的產生器.
給定一個函數rand()能產生0到n-1之間的等機率隨機數,問如何產生0到m-1之間等機率的隨機數?
如何產生以下機率的隨機數?0出1次,1出現2次,2出現3次,n-1出現n次?
5.1-2 描述random(a,b)過程的一種實現,它只調用random(0,1).做爲a和b的函數,你的程序的指望運行時間是多少?
注:random(a,b)爲產生a,a+1,a+2,...,b的函數發生器,且產生各整數的機率相等,同爲1/(b - a + 1).
看到這個題目時,似曾相識,腦海浮現了利用random(0,1)產生0或1,從而組成二進制數,來完成random(a,b)的實現.可是細想之後,感受有個問題在腦海中有點不明不白.
運行random(0,1)函數k次,使得2k>=(b-a+1),將獲得[0,2k)的整數區間,如何將[0,2k)映射到[a,b]的整數區間,保證產生各整數的機率相等,同爲1/(b-a+1).
1.當存在k使得2k=(b-a+1)時,只需將產生的二進制數與[a,b]整數一一對應,便可知足機率同爲1/(b-a+1)的要求.
例如,random(3,6),k=2. 此時,對應關係可爲00~3,01~4,10~5,11~6.產生的機率爲1/4.
2.當不存在k使得2k=(b-a+1)時,產生[0,2k)區間整數的機率爲1/2k,小於1/(b-a+1).[0,2k)如何映射到[a,b]整數區間.
思路一:擴大[0,2k)區間,使得2k能夠被(b-a+1)整除,這樣能夠把[0,2k)分紅N段時,每一段對應[a,b]裏的一個整數.
但這個思路,是不可行的,由於不存在這樣的k值.要麼2k=(b-a+1),要麼2k>(b-a+1)且不可被(b-a+1)整除.
思路二:參取截斷映射,即 [0,2k) 的前部分映射到[a,b],這樣雖然能夠達到產生整數的機率相等,但不等於1/(b-a+1),還有若是產生[0,2k)後部分的值如何處理.
這個思路,是可行的,若是產生後部分的值,就繼續調用自身,從新random.從結果輸出分析,最終random(a,b)最終輸出的只有[a,b]裏的整數,並且每一個整數的機率相等,於是其產生的機率值是1/(b-a+1).
具體的實現代碼以下:
int random(int a,int b) { int m = 1; int len = b - a + 1; int k = 0; //計算最小的正整數k,使2^k >= len while(m < len) { k++; m *= 2; } m = 0; for(int i = 0;i < k;i++) { m += random(0,1) * (1<<i); } if(m + 1 > len) { return random(a,b); } else { return m + a; } }
因爲冗餘的存在,該方法運行時間最壞的狀況是無究,就是無限地遞歸調用自身.運行時間的下限是O(log(b-a+1)).
由上述的練習題可擴展出更多相似的問題.
利用rand5()產生rand7().rand5()產生1到5的整數,rand7()產生1到7的整數.
解決思路與上述的練習題是同樣的.利用rand5()產生的一個整數空間,而後將其映射到[1,7]的整數空間上,映射時保證機率相等,且等於1/7.
下面介紹幾個有意思的實現.
1.利用預置數組 該方法簡單,易理解,可是不具擴展性,須要額外存儲空間.
1 int rand7() 2 { 3 int vals[5][5] = { 4 {1,2,3,4,5}, 5 {6,7,1,2,3}, 6 {4,5,6,7,1}, 7 {2,3,4,5,6}, 8 {7,0,0,0,0} 9 }; 10 int result = 0; 11 while(result == 0) 12 { 13 int i = rand5(); 14 int j = rand5(); 15 result = vals[i - 1][j - 1]; 16 } 17 return result; 18 }
2.常規實現方法 可擴展,主要分爲三步,構造大的整數區間,限制整數區間,最後映射整數區間.
1 int rand7() 2 { 3 int i; 4 do{ 5 i = 5 * (rand5() - 1) + rand5(); //產生[1,25]的整數區間 6 }while(i > 21); //將[1,25]整數區間控制於[1,21] 7 return i%7 + 1; //將[1,21]映射到[1,7] 8 }
3.看似正確的方法 其實錯誤的方法
1 int rand7() 2 { 3 int i; 4 i = rand5() + rand5() + rand5() + rand5() + rand5() + rand5() + rand5(); 5 return i%7 + 1; 6 }
與方法2的思路同樣,構造新的整數區間,可是方法3中構造的整數區間並非等機率的.
第4代碼中,將會產生5^7種可能的計算,但最終這些可能映射到[7,35]的整數區間中,可是[7,35]區間內整數的產生的機率並不相等.
例如,經過累加區間[0,1]三次,能夠獲得[0,3]的區間,可是[0,3]每一個整數的機率並不相等,分別爲1/8,3/8,3/8,1/8.
給你一個能生成1到5隨機數的函數,用它寫一個函數生成1到7的隨機數。 (即:使用函數rand5()來實現函數rand7())。
rand5能夠隨機生成1,2,3,4,5;rand7能夠隨機生成1,2,3,4,5,6,7。 rand5並不能直接產生6,7,因此直接用rand5去實現函數rand7彷佛不太好入手。 若是反過來呢?給你rand7,讓你實現rand5,這個好實現嗎?
一個很是直觀的想法就是不斷地調用rand7,直到它產生1到5之間的數,而後返回。 代碼以下:
int Rand5(){ int x = ~(1<<31); // max int while(x > 5) x = Rand7(); return x; }
1 2 3 4 5 6 |
int Rand5(){ int x = ~(1<<31); // max int while(x > 5) x = Rand7(); return x; } |
等等,這個函數能夠等機率地產生1到5的數嗎?首先,它確確實實只會返回1到5這幾個數, 其次,對於這些數,都是由Rand7等機率產生的(1/7),沒有對任何一個數有偏袒, 直覺告訴咱們,Rand5就是等機率地產生1到5的。事實呢?讓咱們來計算一下, 產生1到5中的數的機率是否是1/5就OK了。好比說,讓咱們來計算一下Rand5生成1 的機率是多少。上面的函數中有個while循環,只要沒生成1到5間的數就會一直執行下去。 所以,咱們要的1多是第一次調用Rand7時產生,也多是第二次,第三次,…第n次。 第1次就生成1,機率是1/7;第2次生成1,說明第1次沒生成1到5間的數而生成了6,7, 因此機率是(2/7)*(1/7),依次類推。生成1的機率計算以下:
P(x=1)=1/7 + (2/7) * 1/7 + (2/7)^2 * 1/7 + (2/7)^3 * 1/7 + ... =1/7 * (1 + 2/7 + (2/7)^2 + ...) // 等比數列 =1/7 * 1 / (1 - 2/7) =1/7 * 7/5 =1/5
1 2 3 4 5 6 |
P(x=1)=1/7 + (2/7) * 1/7 + (2/7)^2 * 1/7 + (2/7)^3 * 1/7 + ... =1/7 * (1 + 2/7 + (2/7)^2 + ...) // 等比數列 =1/7 * 1 / (1 - 2/7) =1/7 * 7/5 =1/5
|
上述計算說明Rand5是等機率地生成1,2,3,4,5的(1/5的機率)。從上面的分析中, 咱們能夠獲得一個通常的結論,若是a > b,那麼必定能夠用Randa去實現Randb。其中, Randa表示等機率生成1到a的函數,Randb表示等機率生成1到b的函數。代碼以下:
// a > b int Randb(){ int x = ~(1<<31); // max int while(x > b) x = Randa(); return x; }
1 2 3 4 5 6 7 |
// a > b int Randb(){ int x = ~(1<<31); // max int while(x > b) x = Randa(); return x; } |
回到正題,如今題目要求咱們要用Rand5來實現Rand7,只要咱們將Rand5 映射到一個能產生更大隨機數的Randa,其中a > 7,就能夠套用上面的模板了。 這裏要注意一點的是,你映射後的Randa必定是要知足等機率生成1到a的。好比,
Rand5() + Rand5() - 1
1 2 |
Rand5() + Rand5() - 1
|
上述代碼能夠生成1到9的數,但它們是等機率生成的嗎?不是。生成1只有一種組合: 兩個Rand5()都生成1時:(1, 1);而生成2有兩種:(1, 2)和(2, 1);生成6更多。 它們的生成是不等機率的。那要怎樣找到一個等機率生成數的組合呢?
咱們先給出一個組合,再來進行分析。組合以下:
5 * (Rand5() - 1) + Rand5()
1 2 |
5 * (Rand5() - 1) + Rand5()
|
Rand5產生1到5的數,減1就產生0到4的數,乘以5後能夠產生的數是:0,5,10,15,20。 再加上第二個Rand5()產生的1,2,3,4,5。咱們能夠獲得1到25, 並且每一個數都只由一種組合獲得,即上述代碼能夠等機率地生成1到25。OK, 到這基本上也就解決了。
套用上面的模板,咱們能夠獲得以下代碼:
int Rand7(){ int x = ~(1<<31); // max int while(x > 7) x = 5 * (Rand5() - 1) + Rand5() // Rand25 return x; }
1 2 3 4 5 6 |
int Rand7(){ int x = ~(1<<31); // max int while(x > 7) x = 5 * (Rand5() - 1) + Rand5() // Rand25 return x; } |
上面的代碼有什麼問題呢?可能while循環要進行不少次才能返回。 由於Rand25會產生1到25的數,而只有1到7時才跳出while循環, 生成大部分的數都捨棄掉了。這樣的實現明顯很差。咱們應該讓捨棄的數儘可能少, 因而咱們能夠修改while中的判斷條件,讓x與最接近25且小於25的7的倍數相比。 因而判斷條件可改成x > 21,因而x的取值就是1到21。 咱們再經過取模運算把它映射到1-7便可。代碼以下:
int Rand7(){ int x = ~(1<<31); // max int while(x > 21) x = 5 * (Rand5() - 1) + Rand5() // Rand25 return x%7 + 1; }
1 2 3 4 5 6 |
int Rand7(){ int x = ~(1<<31); // max int while(x > 21) x = 5 * (Rand5() - 1) + Rand5() // Rand25 return x%7 + 1; } |
這個實現就比上面的實現要好,而且能夠保證等機率生成1到7的數。
讓咱們把這個問題泛化一下,從特殊到通常。如今我給你兩個生成隨機數的函數Randa, Randb。Randa和Randb分別產生1到a的隨機數和1到b的隨機數,a,b不相等 (相等就不必作轉換了)。如今讓你用Randa實現Randb。
經過上文分析,咱們能夠獲得步驟以下:
// A > b int Randb(){ int x = ~(1<<31); // max int while(x > b*(A/b)) // b*(A/b)表示最接近A且小於A的b的倍數 x = RandA(); return x%b + 1; }
1 2 3 4 5 6 7 |
// A > b int Randb(){ int x = ~(1<<31); // max int while(x > b*(A/b)) // b*(A/b)表示最接近A且小於A的b的倍數 x = RandA(); return x%b + 1; } |
Randab = b * (Randa - 1) + Randb Randab = a * (Randb - 1) + Randa
1 2 3 |
Randab = b * (Randa - 1) + Randb Randab = a * (Randb - 1) + Randa
|
若是再通常化一下,咱們還能夠把問題變成:給你一個隨機生成a到b的函數, 用它去實現一個隨機生成c到d的函數。