這篇筆者認為寫得沒系統,對初學者而言不夠清楚 ,有些地方也寫錯,有興趣可參考另一篇
[亂數] <細說> C/C++ 亂數基本使用與常見問題
,本文予以關閉。
前言:
1. 亂數部份吾人有所「輕微長進」,有空時將再為此文進行修改,以期本文更加完整。錯誤部份、不佳部份並不予以刪除,因這些極有可能為他人會犯之點。吾人亂數部份受Jacob Lee 指導甚多,在此特別感謝!
2. 亂數的分佈情形不只一種,這裡探討的是「均勻分佈」 (其它分佈有機會再補上)。
3. 待補上 Knuth-shfftle
----------------- 分隔線 ----------------- 分隔線 ----------------- 分隔線 -----------------
1. 初始化
C / C++ 底上,若要產生一亂數會用到的表頭檔如下:
#include <stdlib.h> // srand 與 rand, C++ 為 cstdlib
#include <time.h> // time(), C++ 為 ctime
在初始化時, 必須先設定亂數種子,否則每次跑出來的亂數都一樣,且需注意件事,以 time 為亂數種子時,仍每秒更新一次,故 srand 事實上沒必要(也不能)放在 loop 裡面一直執行。設定亂數種子方法如下所示:
srand(time(NULL));
另外,整個程式中,亂數種子只需設一次便可,所以 srand 沒必要放在回圈裡面,那只會使執行速度變慢。C 裡之 rand() 使用的是 uniform distribution。其餘的 bernoulli、binomial、... etc,去 MSDN 下載 Visual C++ 2008 Feature Pack Release,裡面有進行支援。
2. 取得亂數
接著呼叫 rand() 便可產生隨機亂數,隨機亂數的範圍為 0 ~RAND_MAX,RAND_MAX 為一 macro,在我的電腦與Complier 底下是被定義為 32767。以下例子便示範跑 100 遍亂數。
for(int i=0; i<100; i++) rand();
3. 設亂數範圍 (1~6之整數)
像是擲骯子,它的點數就只有 1 ~ 6 點,我們可以這麼做
rand()%6 + 1; // worst
rand() %6 的結果一定是落於 0~5 間之整數,把這結果加1後,便成為 1~6 之間的整數。
[Lemma]
這種方式重覆機率很高,對於「均勻」條件差了點,使用下面方式較為佳: (int) ( (6-1) * (double)rand()/RAND_MAX )+1;
4. 設亂數範圍 ([-2,2] 之整數)
這裡和上面差不多,以整數來看,-2~+2 中間有5個整數 (-2,-1,0,1,2),於是我們可以這麼做
rand()%5-2;
rand()%5 之結果必落於 0~4 之間,把這結果減2後,便成為 -2~+2 之整數。於是我們可得到一歸納,要取得 low 與 up 之間之整數亂數為
rand()%(up-low+1) + low
[Lemma]
為符合均勻性,較常使用這種方式做: (up-low) * (double)rand()/RAND_MAX + low; 此法不論是整數或浮點數均適用!若浮點數要考慮是小數後幾位,可再用此法進行四捨五入等方式做調整。
5. 設亂數範圍 ( [-1,1] 之浮點數 )
(5.1) 設 [0,1] 之浮點數亂數
剛有介紹過, 使用 rand 時,其最大值為 RAND_MAX, 於是若要設 0~1 亂數便這麼做
(double)rand()/RAND_MAX;
加上 double 是為了強制轉成浮點數,避免產生相除等於零之情形。
[Lemma]
加註區間性問題 ( 感謝 Jacob Lee 提醒 )
產生 [0,1) 之浮點數:(double)rand() / (RAND_MAX+1);
產生 [0,1] 之浮點數:(double)rand() / RAND_MAX;
產生 (0,1) 之浮點數:(double) (rand()+1) / (RAND_MAX +2); // 思考是否有其他問題存在中
產生 (0,1] 之浮點數:(double) (rand()+1) / (RAND_MAX +1); // 思考是否有其他問題存在中
(5.2) 產生 [-1,+1] 之浮點亂數
這種做法起碼三種, 我只說較簡單的方法。雖產生 [0,1] , 但實際上要產生的是 [-1,1] , 由於間隔為2,所以把分子乘以二倍再除上 RAND_MAX。
(double)rand()*2 / RAND_MAX + (-1) ;
於是我們產生了一通式:[up, down] ---> (double)rand()*(up-down) / RAND_MAX + down;
6. 取得 N 個 1~X不重覆亂數, 其中 X>N
(6.1) 撲克洗牌法
建立 X 張撲克牌,點數為 1~X ,隨機抽二張牌交法(抽的次數有人說 X/2 次,也有人說抽 X 次),最後再從牌堆裡面拿出 N 張出來。
int i, t1, t2, Array[X],tmp; // x 張 poker
for(i=0; i<N; i++) Array[i] = i+1; // 產生 1~N 之點數
for(i=0; i<X; i++){ // 從牌堆抽二張交換
t1 = rand() % X, t2 = rand() % X;
tmp = Array[t1];
Array[t1] = Array[t2];
Array[2=t2] = tmp;
} // 洗牌完畢
// 發出前 N 張
for(i=0; i<N; i++) // printf("%d\n", Array[i]);
(6.2) 改良式 poker
上一個例子我們是隨意抽二張, 但有些人認為要每一張都抽過, 於是就變成, 第一張先選定, 抽一張跟第一張換; 第二張再選定, 抽一張和第二張換... 第 X 張選定, 抽一張與第 X 張換。
int i, pos ,tmp, array[i];
for(i=0; i<X; i++) array[i] = i+1; // 產生點數
for(i=0; i<X; i++) { // 依序交換每一張
pos = rand()%X;
tmp = array[i];
array[i] = array [pos];
array[pos] = tmp;
}
(6.3) Kunth-shfftle (待補)
7. 亂數所需範圍超過 RAND_MAX
Y=AX+B, 現在假設要產生 [0,100000] 亂數, 比我手邊的 32767 還大, 要怎麼產生 ?
32767 * 2 = 65534 < 100000
32767 * 3 = 98301 < 100000
32767 * 4 = 131068 > 10000
於是我將所產生出之亂數乘上 4 倍 再 mod 10000。
rand()*4%10000;
有時這樣便可滿足需求,有時則否。不能滿足的原因大多如下
rand() = 1 -> 1*4 % 100000 = 4
rand() = 2 -> 2*4 % 100000 = 4
沒錯, 這樣下去產生的亂數永遠都是 4 的倍數, 可參考以下之解法
(rand()*4 % 100000 + rand()%4) % 100000;
我以前是這麼做,但說真的連我自己都覺得麻煩、很糟。
---------- 分隔線 ---------- 分隔線---------- 分隔線---------- 分隔線 -------
另可考慮第二種做法:
rand() 範圍為 0~ RAND_MAX = 0~32767,使用了 15 bits;於是可考慮:
int rnd = rand() | (rand() << 15) ;
這樣便生成了 30 bits 之亂數,範圍便展開成了 0~ 2^30 - 1 = 1,073,741,824 (約10億, 10^9),到時再進行 mod 運算,但這種方式絕不可能會使得 rnd=0 ( 連續取二個 rand() 若數值相同,那這個產生器是失敗的!)
類似要擴展的方式也類似,但聲明,這些方式都有著「不均勻」問題,同時有些數字可能都不會出現的問題
int rand31(){
// RAND31_MAX = 2147483647
return ( (rand() << 16) | (rand() << 1) | (rand() & 1) );
}
unsigned int rand32() {
// RAND32_MAX = 4294967295
return ( (rand() << 17) | (rand() << 2) | (rand() & 3) );
}
要徹底解決這個問題, 我想除了找 lib, 換 complier, 再來就是自己寫亂數產生器了。
8. 其他函式庫
(8.1) c++ STL - random_shuffle
使用 random_shuffle 去洗
const int N = 8;
int A[] = {1, 2, 3, 4, 5, 6, 7, 8};
random_shuffle(A, A + N);
(8.2) 網路提供之函式庫與說明
Mersenne Twister : Wiki C/C++ download
Visual C++ 2008 Feature Pack Release:這個強烈建議一定要去載來安裝!裡面加強了許多功能,其中亂數也加強了很多 (待吾人研究後慢慢發心得)
---------- 分隔線 ---------- 分隔線---------- 分隔線---------- 分隔線 -------
[亂數可研究議題、文獻甚多,若有興趣可再深入研究。]