1. 程式人生 > >已知一個函式rand7()能夠生成1-7的隨機數,請給出一個函式rand10(),該函式能夠生成1-10的隨機數。

已知一個函式rand7()能夠生成1-7的隨機數,請給出一個函式rand10(),該函式能夠生成1-10的隨機數。

題目:

已知一個函式rand7()能夠生成1-7的隨機數,請給出一個函式,該函式能夠生成1-10的隨機數。

思路:

假如已知一個函式能夠生成1-49的隨機數,那麼如何以此生成1-10的隨機數呢?

解法:

該解法基於一種叫做拒絕取樣的方法。主要思想是隻要產生一個目標範圍內的隨機數,則直接返回。如果產生的隨機數不在目標範圍內,則丟棄該值,重新取樣。由於目標範圍內的數字被選中的概率相等,這樣一個均勻的分佈生成了。

顯然rand7至少需要執行2次,否則產生不了1-10的數字。通過執行rand7兩次,可以生成1-49的整數,

   1  2  3  4  5  6  7 1  1  2  3  4  5  6  7 2  8  9 10  1  2  3  4
3 5 6 7 8 9 10 1 4 2 3 4 5 6 7 8 5 9 10 1 2 3 4 5 6 6 7 8 9 10 * * 7 * * * * * * *
由於49不是10的倍數,所以我們需要丟棄一些值,我們想要的數字範圍為1-40,不在此範圍則丟棄並重新取樣。

程式碼:

  1. int rand10() {  
  2.   int row, col, idx;  
  3.   do {  
  4.     row = rand7();  
  5.     col = rand7();  
  6.     idx = col + (row-1)*7;  
  7.   } while (idx > 40);  
  8.   return 1 + (idx-1)%10;  
  9. }  

由於row範圍為1-7,col範圍為1-7,這樣idx值範圍為1-49。大於40的值被丟棄,這樣剩下1-40範圍內的數字,通過取模返回。下面計算一下得到一個滿足1-40範圍的數需要進行取樣的次數的期望值:
E(# calls to rand7) = 2 * (40/49) +                       4 * (9/49) * (40/49) +                       6 * (9/49)2 * (40/49) +                       ...                                             = 
2k * (9/49)k-1 * (40/49) k=1 = (80/49) / (1 - 9/49)2 = 2.45
優化:

上面的方法大概需要2.45次呼叫rand7函式才能得到1個1-10範圍的數,下面可以進行再度優化。

對於大於40的數,我們不必馬上丟棄,可以對41-49的數減去40可得到1-9的隨機數,而rand7可生成1-7的隨機數,這樣可以生成1-63的隨機數。對於1-60我們可以直接返回,而61-63則丟棄,這樣需要丟棄的數只有3個,相比前面的9個,效率有所提高。而對於61-63的數,減去60後為1-3,rand7產生1-7,這樣可以再度利用產生1-21的數,對於1-20我們則直接返回,對於21則丟棄。這時,丟棄的數就只有1個了,優化又進一步。當然這裡面對rand7的呼叫次數也是增加了的。程式碼如下:

  1. int rand10Imp() {  
  2.   int a, b, idx;  
  3.   while (true) {  
  4.     a = rand7();  
  5.     b = rand7();  
  6.     idx = b + (a-1)*7;  
  7.     if (idx <= 40)  
  8.       return 1 + (idx-1)%10;  
  9.     a = idx-40;  
  10.     b = rand7();  
  11.     // get uniform dist from 1 - 63  
  12.     idx = b + (a-1)*7;  
  13.     if (idx <= 60)  
  14.       return 1 + (idx-1)%10;  
  15.     a = idx-60;  
  16.     b = rand7();  
  17.     // get uniform dist from 1-21  
  18.     idx = b + (a-1)*7;  
  19.     if (idx <= 20)  
  20.       return 1 + (idx-1)%10;  
  21.   }  
  22. }  
下面計算下優化後方法的呼叫rand7函式的期望次數:
E(# calls to rand7) = 2 * (40/49) +                       3 * (9/49) * (60/63) +                       4 * (9/49) * (3/63) * (20/21) +                         (9/49) * (3/63) * (1/21) *                       [ 6 * (40/49) +                         7 * (9/49) * (60/63) +                         8 * (9/49) * (3/63) * (20/21) ] +                        ((9/49) * (3/63) * (1/21))2 *                       [ 10 * (40/49) +                         11 * (9/49) * (60/63) +                         12 * (9/49) * (3/63) * (20/21) ] +                       ...                      = 2.2123
這裡期望次數為2.21,比起未優化的2.45次減少了大概10%。