1. 程式人生 > >c++ primer 隨機數講解

c++ primer 隨機數講解

原理 C++產生隨機數 隨機數 rand函式產生的是偽隨機數,也就是說它不是一個真實的隨機數。它的原理大概如下: 如果約定:a1=f(seed),an+1=f(an)那你可以得到一個序列:a1,a2,a3…an,那麼要製作一個偽隨機函式rand,只需要讓它每呼叫一次就返回序列的下一個元素就行。 就相當於第1次呼叫rand返回a1,第2次返回a2,…,第n次返回an,這樣每次調rand都能拿到一個不同的數,只要整個序列的規律不明顯,整個函式看起來就是隨機的。 現在計算機上的rand函式都是用這樣的原理實現的,這裡的seed被稱為“隨機數種子”。 所以程式跑一遍退出後,再重新跑一遍,兩次輸出的結果是相同的。如下: #include #include int main() { int a=rand();//每次都是輸出41 printf("%d\n",a); return 0; }

我們知道rand()函式可以用來產生隨機數,但是這不是真正意義上的隨機數,是一個偽隨機數,是根據一個數(我們可以稱它為種子)為基準以某個遞推公式推算出來的一系列數,當這系列數很大的時候,就符合正態公佈,從而相當於產生了隨機數,但這不是真正的隨機數,當計算機正常開機後,這個種子的值是定了的,除非你破壞了系統。 但這裡有一個問題,如果seed不變,那我們每次呼叫rand函式獲取的序列都是相同的。所以我們還需要一個介面去設定seed值,這個介面就是srand函式。   函式介紹 1.rand() 功能:隨機數發生器 用法:intrand(void) 所在標頭檔案:stdlib.h rand()返回一隨機數值的範圍在0至RAND_MAX間。RAND_MAX的範圍最少是在32767之間(int)。用unsignedint雙位元組是65535,四位元組是4294967295的整數範圍。0~RAND_MAX每個數字被選中的機率是相同的。 使用者未設定隨機數種子時,系統預設的隨機數種子為1。 rand()產生的是偽隨機數字,每次執行時是相同的;若要不同,用函式srand()初始化它。

2.srand() 功能:初始化隨機數發生器 用法:voidsrand(unsignedintseed) 所在標頭檔案:stdlib.h srand()用來設定rand()產生隨機數時的隨機數種子。引數seed必須是個整數,如果每次seed都設相同值,rand()所產生的隨機數值每次就會一樣。

3.使用當前時鐘作為隨機數種子 rand()產生的隨機數在每次執行的時候都是與上一次相同的。若要不同,用函式srand()初始化它。可以利用srand((unsignedint)(time(NULL))的方法,產生不同的隨機數種子,因為每一次執行程式的時間是不同的。但要注意,time(NULL)的值是隔1秒才改變一次的,必要情況下可以考慮使用精度更高的時間函式,如gettimeofday。

4.產生隨機數的用法 1)給srand()提供一個種子,它是一個unsignedint型別; 2)呼叫rand(),它會根據提供給srand()的種子值返回一個隨機數(在0到RAND_MAX之間); 3)根據需要多次呼叫rand(),從而不間斷地得到新的隨機數; 4)無論什麼時候,都可以給srand()提供一個新的種子,從而進一步“隨機化”rand()的輸出結果。 #include #include<stdlib.h> #include<time.h> usingnamespacestd; intmain() { srand((unsigned)time(NULL)); for(inti=0;i<10;i++) cout<<rand()<<’\t’; cout<<endl; return0; } 5.產生一定範圍隨機數的通用表示公式 要取得[a,b)的隨機整數,使用(rand()%(b-a))+a; 要取得[a,b]的隨機整數,使用(rand()%(b-a+1))+a; 要取得(a,b]的隨機整數,使用(rand()%(b-a))+a+1; 通用公式:a+rand()%n;其中的a是起始值,n是整數的範圍。 要取得a到b之間的隨機整數,另一種表示:a+(int)b*rand()/(RAND_MAX+1)。 要取得0~1之間的浮點數,可以使用rand()/double(RAND_MAX)。 //但是這種方法不正確,原因是隨機整數的精度通常低於隨機浮點數,這樣,有一些浮點值就永遠不會被生成了。 【說明】 以前一般使用隨機數用rand函式,rand函式有很多缺點。現在標準庫引入了新的隨機數引擎,對於生產隨機數更加方便且功能強大了。 它一般由引擎+分佈型別的方式來生成隨機數。 【使用】 它需包含標頭檔案 #include

直接用引擎生成兩個隨機數: default_random_engine e; for (size_t i = 0; i < 2; i++) { cout << e() << " "; } cout << endl; //生成範圍 cout << “min:” << e.min() << “max:” << e.max() << endl;

上面的使用方式類似於rand。一般實際使用還需配合分佈型別,如下: //定義0到9的分佈型別 uniform_int_distribution u(0, 9); for (size_t i = 0; i < 2; i++) { cout << u(e) << " "; } cout << endl; uniform_int_distribution此型別生成均勻分佈的unsigned值。分佈型別定義了一個呼叫運算子,它接收一個隨機數引擎作為引數。分佈物件使用它的引擎引數生成隨機數,並將其對映到指定的分佈。注意不能使用u(e()),因為我們傳遞的是引擎本身,而不是它生成的下一個值。

當然這些隨機數每次重新開啟生成還是一樣的,這就需要隨機數種子了,種子是在引擎構建時傳入了,一般我們用time(0): //構建隨機數引擎,傳入隨機種子 default_random_engine e2(time(0)); //浮點0到1 uniform_real_distribution d(0, 1); for (size_t i = 0; i < 2; i++) { cout << d(e2) << " "; } 上面產生了0到1之間的隨機數。

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

隨機數引擎操作 Engine e; 預設建構函式;使用該引擎型別預設種子 Engine e(s); 使用整型值s作為種子 e.seed(s) 使用種子s重置引擎的狀態 e.min() 此引擎可生成的最小值 e.max() Engine::result_type 此引擎生成的unsigned整型型別 e.discard(u) 將引擎推進u步;u的型別為unsigned long long

分佈型別 uniform_int_distribution 均勻分佈的隨機整型(和上面的 int rand(); 功能類似) uniform_real_distribution 均勻分佈的隨機浮點型 bernoulli_distribution 隨機布林型(這個型別可以允許調整概率 b(.55)這樣就有55/45 括號表示返回true的概率) binomial_distribution 二項分佈的隨機整型 binomial_distribution 幾何分佈的隨機整型 exponential_distribution 指數分佈的隨機整型 等等 分佈型別從操作 Dist d; //預設建構函式,使d 準備好被使用;其他建構函式依賴於Dist的型別 分佈型別的建構函式時explicit d(e) //用相同的e連續呼叫d的話,會根據d的分散式型別生成一個隨機數序列,e是 一個隨機數引擎物件 d.min //返回d(e)能生成的最小值 d.max // d.rest() //重建d 的狀態,使得隨後對d的使用不依賴於已經生成的值

不重複隨機數 1.方法一 最初的思想是每生成一個隨機數,便於前面的所有隨機數進行比較,如果有重複,則捨去不要,重新選取。但該方法十分費時,並且在資料量巨大的並且有一定限制的時候,會引發巨大問題。例如要生成10000個隨機數,範圍是0-9999,且不能重複,那麼最後幾個隨機數有可能需要相當長的時間才能篩選出來。 下面我們從另外一個角度來思考,假設我們已經由一個數組長度為10000的陣列,裡面分別儲存了資料0-9999,我現在的做法是想辦法讓10000個數進行隨機排列,便得到了這樣一個隨機數列,如果我只要其中的100個數,那麼從前面取出100個就好。這裡利用algorithm.h裡面的一個函式,來進行簡單處理。 template voidrandom_shuffle( RandomAccessIterator_First, RandomAccessIterator_Last ); 這個函式操作的物件是容器的迭代器,即我們需要將儲存資料從陣列變為容器就好了,下面程式碼實現一下: 複製程式碼 #include #include #include usingnamespacestd; voidrandperm(intNum) { vectortemp; for(inti=0;i<Num;++i) { temp.push_back(i+1); } random_shuffle(temp.begin(),temp.end()); for(inti=0;i<temp.size();i++) { cout<<temp[i]<<""; } } cout<<endl; 2.方法二 按順序產生這些數,但隨機產生它們的位置。例如下面產生100個100以內不重複隨機數的程式碼: inta[100]; for(i=0;i<=99;++i)a[i]=i; for(i=99;i>=1;–i)swap(a[i],a[rand()%i]); 上面這段程式碼只需要遍歷一次就可以產生這100個不重複的隨機數,它是如何做到的呢?首先第二行按順序用0到99填滿整個陣列;第三行,是隨機產生從0到m-2個數組下標,把這個下標的元素值跟m-1下標的元 素值交換,一直進行到下標為1的元素。因此它只需要遍歷一次就能產生全部的隨機數。 再看下面的程式碼,原理跟上面例子相似,但效率比上面的差點,但仍不失為一個好方法: 複製程式碼 inta[100]={0}; inti,m; for(i=1;i<=99;++i) { while(a[m=rand()%100]); a[m]=i; } 這段程式碼也是隨機產生位置,但它預先把整個陣列初始化為0,然後隨機產生其中一個位置,如果該元素值為0,表示這個位置還沒有被使用過,就把i賦予它;否則,就重新隨機產生另一個位置,直到整個陣列 被填滿。這個方法,越到後面,遇到已使用過的元素的可能性越高,重複次數就越多,這是不及第一個方法的地方,但總的來說,效率還是不錯的。