相對於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