[轉]C++11 隨機數學習


相對於C++ 11之前的隨機數生成器來說,C++11的隨機數生成器是復雜了很多。這是因為相對於之前的只需srand、rand這兩函數即可獲取隨機數來說,C++11提供了太多的選擇和東西。

 

隨機數生成算法:
        隨機數生成算法有很多,C++11之前的C/C++只用了一種。C++11則提供下面三種可供選擇:

linear_congruential_engine線性同余法
mersenne_twister_engine梅森旋轉法
substract_with_carry_engine滯后Fibonacci
        這三種算法,在C++11里面都是用模板的方式實現的。如果我們要使用這三個模板類的話,就必須自己實例化之。但這些實例化參數都是這些算法里面使用到的參數,如果不懂算法的原理的話,真的不知道需要用什么參數才能得到比較好的隨機序列。所以我們這些卑微的碼農是用不了這些模板類的。C++11標准也想到了這點,所以就幫我們預定義了一些隨機數類,這些隨機數類都是用比較好的參數實例化上面那三個模板類。注意:在C++11里面,把這些隨機數生成器叫做引擎(engines)。

       下圖列出了一些實例化的隨機數類:

       當然具體用了哪些參數,我們是不用管的,直接用就行了。

       在上圖的左上角,還可以看到一個default_random_engine的類。它也是一個實例化的類。之所以不歸入那三種算法,是因為它的實現是由編譯器廠家決定的,有的可能用linear_congruential_engine實現,有的可能用mersenne_twister_engine實現。這種現象在C/C++中見多了。不過,對於其他的類,C++11是有明確規定用哪種算法和參數實現的。

 

       好了,說了這么多還是上一個例子吧。

#include<iostream>
#include<random>
 
usingstd::cout;
usingstd::endl;
usingstd::cin;
 
intmain()
{
    std::default_random_engine random;
 
    for(int i = 0; i < 20; ++i)
        cout<<random()<<' ';
    cout<<endl;
 
    return 0;
}
//gcc編譯器需要加上 –std=c++11 選項。

  

       C++11中,隨機數都是定義在random頭文件中的。除了default_random_engine,其他的那些實例化隨機數類的名字都是怪怪的,所以還是這個好用。從例子中可以看到,是通過operator ()函數來獲取下一個隨機數。
       對srand熟悉的碼農們肯定發現,這里沒有使用到隨機數種子。其實這里使用了默認種子,默認種子的值可以通過這類的公共靜態常量default_seed來獲取。如果想為這個類設置自己的種子的話,那么可以通過在構造函數中傳入一個參數。也可以在構造之后調用seed()成員函數設置種子。

 

產生均勻分布的隨機數:
       上面例子產生的隨機數會比較大,如果我們只想產生0到100的隨機數。按照我們之前的做法是直接random()%100。這種做法是不好的。原因可以參見《Accelerated C++》的7.4.4節。

        C++11也知道這一點,這就使得C++11的隨機數更加復雜了。

       我們平常說產生隨機數,隱含是意思是產生均勻分布的隨機數。學過概率論的同學都知道,除了均勻分布還有很多分布,比如正態分布、泊松分布等等。之前在網上看過網友怎么用rand()函數產生的隨機數制作這些分布。現在這工作不用碼農做了,C++11標准都提供了這些分布。

       C++11提供的均勻分布模板類為:uniform_int_distribution和uniform_real_distribution。前一個模板類名字中的int不是代表整型,而是表示整數。因為它是一個模板類,可以用int、long、short等整數類型來實例化。后一個表示浮點數模板類,可以用float和double來實例化。使用例子如下:

#include<iostream>
#include<random>
#include<time.h>
 
using std::cout;
using std::endl;
using std::cin;
 
 
int main()
{
    std::default_random_engine random(time(NULL));
    std::uniform_int_distribution<int> dis1(0, 100);
   std::uniform_real_distribution<double> dis2(0.0, 1.0);
 
    for(int i = 0; i < 10; ++i)
        cout<<dis1(random)<<' ';
    cout<<endl;
 
    for(int i = 0; i < 10; ++i)
        cout<<dis2(random)<<' ';
    cout<<endl;
 
    return 0;
}

  

        可以看到,在uniform_int_distribution的構造函數中,參數說明了隨機數的范圍。uniform_int_distribution的隨機數的范圍不是半開范圍[  ),而是[  ],對於uniform_real_distribution卻是半開范圍[  )。也是就是說上面的例子中,能產生100,但不會產生1.0。不得不說,這顛覆了之前的認識。對於default_random_engine來說,其產生的隨機數范圍是在[min(), max()]之間,其中min()和max()為它的兩個成員函數。同樣,也是非半開范圍。對於浮點數,如果真的是想產生[0.0, 1.0]范圍的數,可以使用

#include<cmath>
#include<cfloat>
std::uniform_real_distribution<double> dis2(0, std::nextafter(1,DBL_MAX));

  

        如果uniform_int_distribution使用了無參構造函數,那么其隨機數的范圍是[0,numberic_limits<type>::max()],也就是0到對應實例化類型能表示的最大值。對於uniform_real_distribution的無參構造函數,則是[0, 1)。

 

mt19937

當你第一眼看到這玩意兒的時候

肯定禁不住吐槽:納尼?這是什么鬼?

確實,這個東西鮮為人知,但是它卻有着卓越的性能

簡介

mt19937是c++11中加入的新特性

它是一種隨機數算法,用法與rand()函數類似

但是具有速度快,周期長的特點(它的名字便來自周期長度:2^19937-1)

說的直白一點,我們都知道rand()在windows下生成的數據范圍為0-32767

但是這個函數的隨機范圍大概在(maxint,+maxint)(−maxint,+maxint)(maxint為int類型最大值)

實例

這個東西用法非常簡單

#include<random>
#include<ctime>
std::mt19937 rnd(time(0));
int main()
{
    printf("%lld\n",rnd());
    return 0;
}

  

 

概率分布類型:
        C++11提供的概率分布類型有下面這些:

 

均勻分布:

       uniform_int_distribution          整數均勻分布

       uniform_real_distribution        浮點數均勻分布

 

伯努利類型分布:(僅有yes/no兩種結果,概率一個p,一個1-p)

       bernoulli_distribution    伯努利分布

       binomial_distribution     二項分布

       geometry_distribution    幾何分布

       negative_biomial_distribution  負二項分布

 

Rate-based distributions: 

       poisson_distribution 泊松分布

       exponential_distribution指數分布

       gamma_distribution 伽馬分布

        weibull_distribution 威布爾分布

       extreme_value_distribution 極值分布

 

正態分布相關:

       normal_distribution        正態分布

       chi_squared_distribution卡方分布

       cauchy_distribution       柯西分布

       fisher_f_distribution      費歇爾F分布

       student_t_distribution t分布

 

分段分布相關:

       discrete_distribution離散分布

       piecewise_constant_distribution分段常數分布

       piecewise_linear_distribution分段線性分布

 

        這些概率分布函數都是有參數的,在類的構造函數中把參數傳進去即可。

        下面是一個泊松分布的例子

#include<iostream>
#include<random>
#include<time.h>
#include<iomanip>
 
intmain()
{
  const int nrolls = 10000; // number ofexperiments
  const int nstars = 100;   // maximum number of stars to distribute
 
  int parameter = 4;
 
  std::minstd_rand engine(time(NULL));
  std::poisson_distribution<int>distribution(parameter);
 
  int p[20]={};
 
  for (int i=0; i<nrolls; ++i)
  {
    int number = distribution(engine);
    if (number < 20)
        ++p[number];
  }
 
  std::cout << "poisson_distribution"<<parameter<< std::endl;
  for (int i=0; i < 20; ++i)
    std::cout<<std::setw(2)<< i<< ": " << std::string(p[i]*nstars/nrolls, '*') <<std::endl;
 
  return 0;
}

  

        某一個輸出結果為:

 

        可能大家都忘了泊松分布了,看一下下面的圖吧

 

 

真正的隨機數:
        C++11還提供了一個random_device隨機數類。它並不是由某一個數學算法得到的隨機序列,而是通過讀取文件,讀什么文件看具體的實現(Linux可以通過讀取/dev/random文件來獲取)。文件的內容是隨機的,因為文件內容是計算機系統的熵(熵指的是一個系統的混亂程度)。也是當前系統的環境噪聲,系統噪音可以通過很多參數來評估,如內存的使用,文件的使用量,不同類型的進程數量等等。Linux的熵來自鍵盤計時、鼠標移動等。

         不過gcc好像並沒有很好地實現這個類,我手里的Mingw4.9.0就不隨機,每次運行都得到同樣的序列。

 

        對於C++11的隨機類的更多用法可以參考這里。

 

        參考:《C++標准庫 ——自學教程與參考手冊》(第2版)

                   《C++ Primer》(第5版)

                     http://blog.csdn.net/akonlookie/article/details/8223525

                     http://stackoverflow.com/questions/19665818/best-way-to-generate-random-numbers-using-c11-random-library?rq=1

                     http://hipercomer.blog.51cto.com/4415661/857870


免責聲明!

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



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