【算法題】rand5()產生rand7()


前兩天,睡覺前,偶爾翻起算法導論,看到隨機函數這一塊內容,里面有一個練習題.

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.

 

參考資料:

算法導論

http://stackoverflow.com/questions/137783/expand-a-random-range-from-1-5-to-1-7?page=1&tab=votes#tab-top

 

 

——


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM