隨機數問題

來源:互聯網
上載者:User

最初問題:從n個數中隨機播放m個數(0<=m<=n)。

為了便於描述,可以將該問題抽象為:從0-n-1這n個數中隨機播放m個數。電腦能夠提供的隨機數都是偽隨機的,我們假設電腦提供的偽隨機數為真正的隨機。

原創文章,轉載請註明出處:http://blog.csdn.net/fastsort/article/details/10162871

0、產生一個隨機數

系統(c/c++)提供的rand函數只有15位,如果不滿足要求,需要自己擴充,30位的隨機函數如下:

/** @brief 返回一個30bit的隨機數 ** @note   系統內建的rand只有15bit */int     BigRand(){    static  bool    flag=false;    if(flag==false)    {        srand(time(0));        flag = true;    }    return  (rand()<<15)+rand();}

1、最簡單的解法

每次產生一個0-n-1之間的隨機數,放入一個集合中,直到集合的大小為m。C++的STL中有set,比較方便:

void    GetRandNum_set(int m,int n){    cout<<__FUNCTION__<<": ";    set<int>    s;    while(signed(s.size())<m)    {        s.insert(RandInt(0,n-1));    }    set<int>::iterator    i=s.begin();    while(i!=s.end())        cout<<*i++<<" ";    cout<<endl;}

上面的代碼工作沒有問題,但是當m接近n且很大時,最後幾個數的產生將會很困難。因為會產生大量的重複的數。

如何不產生重複的數呢?


2、最多n次的解法

假設當前剩餘m個數要選,

從0開始到n-1這n個數,以m/n的機率選中選中0:總共n個數,要選出m個;

對於1:如果選中0,則以(m-1)/(n-1)的機率選擇1(總共n-1個,要選m-1個);如果沒選中,則以m/(n-1)的機率選(總共n-1個,要選m個);

……

對於i:總共還剩下n-i個,還需要選m個,那麼選中的機率就是m/(n-i)。

沒選中一個,剩餘要選的數就減少一個。

因此代碼如下:

/** @brief 在[0-n)中隨機的選擇m個不同的數 **         並按序輸出 */void    GetRandNumSorted(int m,int n){    cout<<__FUNCTION__<<": ";    if(m<0 || m>=n)  return;    for(int i=0; m!=0 && i<n; i++)    {        if(BigRand()%(n-i)<m)        {            cout<<i<<" ";            m--;        }    }    cout<<endl;}

顯然,這時輸出是從小到大按序選擇的。

其中:if(BigRand()%(n-i)<m) 的機率為:m/(n-i)。
可以分析,每個數選中的機率都是m/n:

數 選中機率

0:  m/n

1: m/n * (m-1)/(n-1)  +  (1-m/n) * m/(n-1) =m/n;

2:    好多項相加,這裡就不寫了。。。

……


3、不按序輸出

如果要求不按序輸出,有兩種解決辦法。

一種是將上面的結果儲存起來,然後再打亂儲存的數組。

還有一種就是直接產生m個隨機數。

先看直接產生m個隨機數,其實就是先從0-n-1中隨機播放一個,作為第一個;然後再從剩下的n-1個數中隨機播放一個作為第二個……直到選出第m個。這就是所謂“完美洗牌”或者打亂數組。

/** @brief 在[0-n)中隨機的選擇m個不同的數 **         並隨機輸出 */void    GetRandNum(int m, int n){    cout<<__FUNCTION__<<": ";    int * p= (int*)malloc(sizeof(int)*n);//!!!    for(int i=0;i<n;i++)        p[i] = i;    ///shuffle p[0...m-1]    for(int i=0; i<m; i++)    {        swap(p[i],p[RandInt(i,n-1)]);        cout<<p[i]<<" ";    }    cout<<endl;    free(p);}

這裡需要一個函數,能夠隨機產生一定範圍內的數:

/** @brief 返回[l,u]之間的一個隨機數 **/int     RandInt(int l, int u){    l = l<u?l:u;    u = l<u?u:l;    return  BigRand()%(u-l+1) + l;}

這種演算法的問題是,如果n很大,m很小,對輔助空間的浪費太嚴重。因為開闢了那麼大的空間,實質只用了很少一部分。

另一種就是先按序隨機播放m個數,然後再打亂:

/** @brief 在[0-n)中隨機的選擇m個不同的數 **         並隨機輸出 */void    GetRandNum2(int m, int n){    cout<<__FUNCTION__<<": ";    int * p= (int*)malloc(sizeof(int)*m);    int tm=m;    for(int i=0,j=0; m!=0 && i<n; i++)    {        if(BigRand()%(n-i)<m)        {            p[j++]=i;//cout<<i<<" ";            m--;        }    }    for(int i=0; i<tm; i++)    {        swap(p[i],p[RandInt(i,tm-1)]);        cout<<p[i]<<" ";    }    cout<<endl;    free(p);}

4、隨機讀取檔案中的一行

在不知道檔案總行數的情況下,隨機讀取檔案中的一行。

最直觀的做法就是,先讀取一次檔案,確定總行數n。然後產生一個1-n的隨機數m,再讀取第m行。顯然這是可行的,但是問題是如果檔案很大,平均要遍曆檔案1.5次。效率很低。

而且如果檔案在不算增長,那麼這個方法就不行了。


通過上面的演算法的啟發,其實也可以唯讀取一次。

首先讀取第一行,如果只有一行,就結束了,設為line;

如果有第2行,那麼以1/2的機率替換line;這時1、2兩行被選中的機率都是1/2.

如果有第3行,那麼以1/3的機率替line;則第3行被選中的機率是1/3,1、2兩行被選中的機率則都是1/2*2/3=1/3.

……

第i行,以1/i的機率替換line。

直到檔案結束。

/** @brief 從檔案fname中隨機讀取一行 */void    GetOneLineRand(const char *fname){    cout<<__FUNCTION__<<": ";    string line,str_save;    ifstream ins(fname);    int cnt=1;    while(getline(ins,line))    {        if(cnt==1)        {            str_save = line;        }        else        {            if(RandInt(1,cnt)==1)///[1,cnt]                str_save = line;        }        cout<<cnt<<" : "<<line<<endl;        cnt++;    }    cout<<"rand line : "<<str_save<<endl;    ins.close();}

這裡的if(RandInt(1,cnt)==1)裡的1,可以是[1,cnt]中任意一個值,機率均為1/cnt。


5、隨機讀取k行

先去讀k行,儲存在一個數組中(假設檔案至少有k行);

然後每讀取一行,都以k/n的機率替換數組中的任意一行,其中n為當前總共讀取的行數。

/** @brief 從檔案fname中隨機讀取k行 */void    GetRandLines(const char *fname, int k){    cout<<__FUNCTION__<<": ";    string  * kstr = new string[k], line;    ifstream ins(fname);    int cnt=1;    while(cnt<=k)///先讀取前k行    {        if(getline(ins,kstr[cnt-1]))   cnt++;        else    break;///檔案沒有k行,直接退出    }    while(getline(ins,line))    {        if(RandInt(1,cnt)<=k)/// p=k/cnt        {            swap(kstr[RandInt(1,k)-1],line);///隨機替換一行        }        cnt++;    }    for(int i=0; i<k ;i++)    {        cout<<kstr[i]<<endl;    }    cout<<endl;    delete[] kstr;    ins.close();}

其他問題請參考《編程珠璣-第12章》。

原創文章,轉載請註明出處:http://blog.csdn.net/fastsort/article/details/10162871

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.