1. 程式人生 > >【C++】c++ 11中的隨機數 ——random

【C++】c++ 11中的隨機數 ——random

c++ 中的隨機數

  在 C++ 程式中,在新標準出現之前,C 和 C++ 都依賴一個簡單的 C 庫函式 rand 來生成隨機數,但是,這個函式生成的是均勻分佈的偽隨機數,每個隨機數的範圍在 0 和一個系統相關的最大值(至少為 32767)之間。

  rand 函式有一些問題:即使不是大多數,也有很多程式需要不通範圍的隨機數。一些應用需要隨機浮點數。一些程式需要非均勻分佈的隨機數。而在編寫程式為了解決這些通常會轉換 rand 生成的隨機數的範圍、型別或者是分佈時,常常會引入非隨機性。

  在 C++ 11 標準中,定義在標頭檔案 random 中的隨機數庫通過一組協作的類來解決這些問題,主要用到的是兩個類:

  • 隨機數引擎類(random-number engines)
  • 隨機數分佈類(random-number distribution)

其中,一個引擎類可以生成 unsigned 隨機數列,一個分佈使用一個引擎類生成指定型別的,在給定範圍內的,服從指定概率分佈的隨機數。

1. 隨機數引擎和分佈

  隨機數引擎是函式物件類,他們定義了一個呼叫運算子,該運算子不接受引數並返回一個隨機的 unsigned 整數。我們可以通過呼叫一個隨機數引擎物件來生成原始隨機數。

default_random_engine e;    // 生成隨機無符號數
for(size_t i=0; i<10
; i++)
// e() “呼叫”物件來生成下一個隨機數 cout << e() <<endl;

在上面這幾行的程式碼中,定義了一個名為 edefault_random_engine 的物件。在 for 迴圈內,我們呼叫物件 e 來獲得下一個隨機數。

1.1 分佈型別和引擎

  為了得到一個在指定範圍內的數,我們一用一個分佈型別的物件:

//生成 0 到 9 之間(包含)均勻分佈的隨機數
uniform_int_distribution<unsigned> u(0,9);
default_random_engine e;    // 生成無符號隨機整數
for (size_t i =0;i<10; i++) // 將 u 作為隨機數源 // 每個呼叫返回在指定範圍內並服從均勻分佈的值 cout<<u(e)<<" "; cout<< endl;

上面的程式碼輸入如下:

0 1 7 4 5 2 0 6 6 9

  上面的程式中,我們將 u 定義為 uniform_int_distribution<unsigned> 。這種型別生成均勻分佈的 unsigned 值。當我們定義一個這種型別的物件時,可以提供想要的最小值和最大值。在上面這段程式碼中,u(0,9) 表示我們希望得到 0 到 9 之間(包含)的數。隨機數分佈類會使用包含的範圍,從而我們可以得到給定整形的每個可能值。

  類似引擎型別,分佈型別也是函式物件類。分佈型別定義了一個呼叫運算子,它接受一個隨機數引擎作為引數。分佈物件使用它的引擎引數生成隨機數,並將其對映到指定的分佈。

  傳遞給分佈物件的是引擎物件本身,也就是 u(e),如果我們將呼叫寫為 u(e()),含義就變為將 e 生成的下一個值傳遞給 u,這會導致一個編譯錯誤。我們傳遞的是引擎本身,而不是他生成的下一個值,原因是某些分佈可能需要呼叫引擎多次才能得到一個值。

1.2 使用引擎生成一個數值序列

  隨機數發生器有一個特性,也就是即使生成的樹看起來是隨機的,但是對於一個給定的發生器,每次執行程式它都會返回相同的數值序列。序列不變這一事實在 除錯 的時候十分有用,但是另一方面,使用隨機數發生器的程式也必須考慮到這一特性。

  下面介紹一個例子,需要一個函式生成一個 vector,包含 100 個均勻分佈在 0 到 9 之間的隨機數。一種錯誤的方法是使用下面的程式碼:

vector<unsigned >bad_randVec()
{
    default_random_engine e;
    uniform_int_distribution<unsigned >u(0,9);
    vector<unsigned >ret;
    for(size_t i = 0;i<100;i++)
        ret.push_back(u(e));
    return ret;
}
// 但是 每次呼叫這個函式都會返回相同的 vector
vector<unsigned >v1(bad_randVec());
vector<unsigned >v2(bad_randVec());
// 將會列印輸出 equal
cout << ((v1==v2) ? "equal" : "not equal") << endl;

  上面這段程式碼會輸出 equal,因為 vector v1 和 v2 具有相同的值。

正確的定義方法是 將引擎和關聯的分佈物件定義為 static 的

 vector<unsigned >good_randVec()
{
    // 由於我們希望引擎和分佈物件保持狀態,因此應該將他們定義為
    // static 的,從而每次呼叫都生成新的數
    static default_random_engine e;
    static uniform_int_distribution<unsigned > u(0,9);
    vector<unsigned > ret;
    for(size_t i = 0; i<100;i++)
        ret.push_back(u(e));
    return ret;
}

由於 eu 都是 static 的,因此它們在函式呼叫之間會保持住狀態。第一次呼叫會使用 u(e) 生成的序列中的前 100 個隨機數,第二次呼叫會獲得接下來 100 個。以此類推。

  注意,一個給定的隨機數發生器已知會生成相同的隨機數序列。一個函式如果定義了局部的隨機數發生器,應該將其(包括引擎和分佈物件)定義為 static 的。否則,每次呼叫函式都會生成相同的序列。

1.3 設定隨機數發生器種子

  隨機數發生器會生成相同的隨機數序列這一特性在除錯中很有用。但是,一旦我們的程式除錯完畢,我們通常希望每次執行程式都會生成不同的隨機結果,可以通過提供一個種子(seed)來達到這個目的。種子就是一個數值,殷勤可以利用它從序列中一個新位置重新開始生成隨機數。

  為引擎設定種子有兩種方式:

  • 在建立引擎物件時提供種子
  • 呼叫引擎的 seed 成員
    // 幾乎肯定是生成隨機整數 vector 的錯誤方法
    // 每次呼叫這個函式都會生成相同的 100 個數
    default_random_engine e1;       // 使用預設種子
    default_random_engine e2(2147483646);       // 使用給定的種子值
    // e3 和 e4 將會生成相同的序列,因為他們使用了相同的種子
    default_random_engine e3;
    e3.seed(32767);             //呼叫 seed 設定為一個新種子值
    default_random_engine e4(32767);    // 將種子值設定為 32767
    for(size_t i = 0;i != 10; i++)
    {
        if (e1() == e2())
            cout<<"unseeded match at iteeration: "<<i<<endl;
        if (e3() != e4())
            cout<<"seeded differs at itertion: "<<i<<endl;
    }

  設定種子最常用的方法是呼叫系統函式 time ,這個函式定義再標頭檔案 ctime 中,它返回一個特定時刻到當前經過了多少秒。函式 time 接受單個指標引數,它指向用於寫入時間的資料結構。如果此指標為空,則函式簡單的返回時間:

default_random_engine e1(time(0));  // 稍微隨機些的種子

但是,由於 time 返回以秒計的時間,因此這種方式只適用於生成種子的間隔為秒級或更長的應用。

2. 其他隨機數分佈

2.1 生成隨機實數

  程式常常需要一個隨機浮點數源。特別是程式經常需要 0 到 1 之間的隨機數。

  可一定以一個 uniform_real_distribution 型別的物件,並讓標準庫來處理從隨機整數到隨機浮點數的對映。與處理 uniform_int_distribution 一樣,在定義物件時,我們指定最小值和最大值。

    default_random_engine e;        // 生成無符號隨機整數
    // 0 到 1 (包含)的均勻分佈
    uniform_real_distribution<double >u(0,1);
    for(size_t i =0;i<10;i++)
        cout<<u(e)<<" ";
    cout<<endl;

此外,當我們對分佈函式不指定預設生成的型別引數時,程式會自動賦予一個型別,生成浮點值得分佈型別預設生成 double 型別,生成整型值的分佈型別預設生成 int 型別,如下:

uniform_real_distribution<>u(-1,1); // 預設生成 double 值

2.2 生成非均勻分佈的隨機數

  除了生成上面的均勻分佈,C++ 11 還規定了可以生成 20 種不同的分佈型別,比如 均勻分佈uniform,正態分佈normal,二項分佈binomial,泊松分佈poisson,學生分佈 student 等等,相關函式可以檢視相應的函式(具體可以參考 C++ Primer 781頁)。