隨機數問題——基礎知識必備

C++標準模板庫規範每次插入操作都在O(log m)時間內完成,而遍歷集合則需要O(m)時間,所以每次查找並插入一個元素的時間是O(mlog m).
一些隨機數的庫函數:(以下來自百度百科)
C庫函數 rand():rand()函數是產生隨機數的一個隨機函數。C語言裏還有srand()函數等。
  (1)使用該函數首先應在開頭包含頭文件stdlib.h

  #include<stdlib.h>

  (2)在標準的C庫中函數rand()可以生成0~RAND_MAX之間的一個隨機數,其中RAND_MAX是stdlib.h 中定義的一個整數,它與系統有關。

  (3)rand()函數沒有輸入參數,直接通過表達式rand()來引用;例如可以用下面的語句來打印兩個隨機數:

  printf("Random numbers are: %i%i\n",rand(),rand());

  (4)因爲rand()函數是按指定的順序來產生整數,因此每次執行上面的語句都打印相同的兩個值,所以說C語言的隨機並不是真正意義上的隨機,有時候也叫僞隨機數。

  (5)爲了使程序在每次執行時都能生成一個新序列的隨機值,我們通常通過爲隨機數生成器提供一粒新的隨機種子。函數 srand()(來自stdlib.h)可以爲隨機數生成器播散種子。只要種子不同rand()函數就會產生不同的隨機數序列。srand()稱爲隨機數生成器的初始化器。

rand()產生僞隨機數,srand函數提供種子,種子不同產生的隨機數序列也不同,所以通常先調用srand函數 time(0)返回的是系統的時間(從1970.1.1午夜算起),單位:秒,種子不同當然產生的隨機數相同機率就很小了。

unsigned int seed;

srand(seed);// 我自己常這樣寫: srand((unsigned int) time(NULL));

rand();//以這三條語句爲基礎,如果seed = time(0) 的話,每次種子不同,就可以產生隨機數了。

注意一點:

  RAND_MAX是VC中stdlib.h中宏定義的一個字符常量:

  #define RAND_MAX 0x7FFF

  其值最小爲32767(2^15 -1, 即0x7FFF-1 ),最大爲2147483647,

  通常在產生隨機小數時可以使用RAND_MAX。

以上是隨機數問題的基礎知識。下面討論幾種方法。

 

題目:給定n個數,存放在數組中,抽取其中的m個數輸出,要求每個數被選中的概率相等。

設數組爲 a[n]

輸出a[0]  的概率爲: m/n;

輸出a[1]  的概率爲: m/n  * (m-1)/(n-1) + (1-m/n)  *  m/(n-1) = m/n;  其中m/n爲算則a[0]的概率,(1-m/n)爲不選擇a[0]的概率,(m-1)/(n-1) 和  m/(n-1)分別表示對於a[0]的兩種情況中選擇a[1] 的概率,最後a[1]被選擇的總概率爲m/n.

以此類推。

以上證明來自概率論的條件概率知識,很容易理解的。

 

解法一:

i=0:rad = rand(), 如果rad%(n-i)=rad%n<m,則輸出a[i],另m=m-1; 如果 如果rad%n>=m,則不輸出a[i], 從剩餘的n-1個數中選出m個隨機數

i=1:rad= rand(), 如果rad%(n-i)=rad%(n-1) <m,則輸出a[i],另m=m-1;如果 如果rad%n>=m,則不輸出a[i], 從剩餘的n-1個數中選出m個隨機數

以此類推。這種方法到底正確不,可以考慮一個極限的情況,就是前 n-m 個數都沒輸出,就剩最後的m個數,理論上講着m個數就必須全部輸出才正確。

到數第m個數時爲

i= n-(m-1) = n-m+1:rad%(n-i) = rad% (n-(n-m+1)=rad%(m-1),這個等式的最後結果肯定屬於[0,m-1]區間內,該區間內的所有數都小於m,所以a[i] 肯定輸出,最後另M=m-1了,M表示要輸出的剩餘數的個數;  

這就是 i 指向a[n]中倒數第m個數的情況,i=i+1,後,i 指向 a[n] 中的倒數m-1個數, 而上面已經輸出一個數了,m已經減掉1了,所以就變成了, i 指向 a[n] 中的倒數m-1個數, 需要輸出的數爲M=m-1個。 又回到了剛纔的狀態。

 

重複以上過程,可知,對於假設的極限情況,就是前 n-m 個數都沒輸出,剩餘的m個數會全部輸出的!OK啦!!

 

代碼:

void rad_m_from_n(int m, int n)
{
        for(i=0; i<n; i++)
        {
               if(rand() %(n-i) <m)
               {
                       cout<<a[i]<<endl;
                       m--;
               }
        }
}


 

解法二:

將原始數組打亂,然後將數組的前m個數輸出。

打亂的方法就是對於每一個a[i] ,從a[i] ~ a[n-1]中隨機挑選一個a[j],交換a[i] a[j] 的值,由於只輸出前m個,所以執行m次循環就可以了。

void rad_m_from_n2(int m, int n)
{
        int i,j;
        for(i=0; i<m; i++)
        {
               j = rand(i,n-1);//從區間[i],n-1]內隨機抽取一個數,函數實現在下面有講解
               int t = a[i];
               a[i] = a[j];
               a[j] = t;
        }
        //sort(a,m)//如果需要按順序輸出的話,就將數組的前m個數排序
        for(i=0; i<m ;i++)
               cout << a[i]<<endl;
}
 


題目變形

變形一:《編程珠璣》第12章中的習題,rand()通常返回約15個隨機位,使用該函數實現函數bigrand()和randint(l,u),要求前者至少返回30個隨即位,後者返回[l,u]範圍內的一個隨機整數。

先來分析bigrand():初始是15位,現在是30位,自然想到了倍數的關係,由於2^30 = 2^15 * 2^15 ,可見bigrand()的返回值最大應當是RAND_MAX *RAND_MAX 了。答案最後爲

int bigrand()
{
       return RAND_MAX*rand() + rand();
}

 

金子分析:這個至少30個隨機位,說實話我還沒有搞懂,應該是最大值至少爲30bits表示的最大數吧,如果按照答案的辦法,產生的隨即數集中在(2^15)~(2^30 -1)吧,確切的邊界我沒有比較,當然前提是rand()不等於0,到底經過隨機後rand()會不會結果爲0,我沒實驗過,上述的辦法導致 1 ~ (2^15 -1) 無法產生阿。

我能想到的一種改進方法是 RAND_MAX*(rand() %2)+rand(),利用第一個rand()產生的數的奇偶性來指定輸出前半段還是後半段的數。

 

randint 就好實現多了

int randint (int l,int u)
{
     return l + bigrand() % ( u-l + 1);//注意邊界不要弄錯了。邊界應該理解爲u - (l -1).
}

 

變形二:(百度的一道題目)

爲了分析用戶的行爲,系統往往需要存儲用戶的一些query,但是因爲query非常多,所以系統不能夠存下每一條。假設我們的系統每天只能夠存儲mquery,現在需要設計一個算法,對用戶時時請求的query進行隨機選擇m個,請給出一個方案,使得每一個query被抽中的概率儘量相等,也請附加相應的分析。需要注意的是,不到最後一刻你並不知道用戶的總請求量是多少。

 

金子分析:本題需要一個假設,假設每天query的總數爲n,題目就變成了n個數,隨機抽取m個數的問題,但是因爲題目不是將數存儲在數組中,而是將抽取的數存放在a[m]中,所以具體實現如下:

1)先將每天的前mquery存放在數組a[m]中。

2

for(i=m; i<n; i++)
    {
          r = rand() % i; //隨機[0,i] 
          if (r>=0 && r <m)
          {
                a[r] = a[i];
           }
      }     
3) 驗證每個query被抽中的概率是否相等。

    首先看a[n-1],它被抽中的概率是 m/n;

     再看a[n-2], 隨機到[0,m-1]的概率爲 m/(n-1),要是它最後存在數組中的前m個,要保證對於a[n-1]時,獲得的隨機數不能與a[n-2]時的隨機數相等,這樣a[n-1]的隨機數有 (n-1)種選擇,則a[n-2]最後存在a[0...m-1]的概率爲 m/(n-1)  *   (n-1)/n = m/n;

    以此類推,對於a[m,...,n-1]來說,被抽中的概率相等,且都爲 m/n.

    

    最後我們在看已經放到數組中的前m個query, 對於a[0], 要想使a[0]不被替換,則從i=m 開始,一直到i=n-1,每個元素的隨機數都不能爲0,

    則 i=m時,隨機數不爲0的概率爲, m/(m+1)

        i = m+1,   爲(m+1)/(m+2).

        ......

        i = n-1時,爲 (n-1)/n.

       最後,a[0]不被替換的概率爲m/(m+1)  * (m+1)/(m+2)   * (m+2)/(m+3)*   ...... * (n-1)/n  = m/n;也就是說,a[0]不被替換,即被抽中的概率是 m/n.

     以上方法得證!













 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章