JAVA自定義演算法產生正態分佈隨機數
一、為什麼需要服從正態分佈的隨機函式
一般我們經常使用的隨機數函式 Math.random() 產生的是服從均勻分佈的隨機數,能夠模擬等概率出現的情況,例如 扔一個骰子,1到6點的概率應該相等,但現實生活中更多的隨機現象是符合正態分佈的,例如20歲成年人的體重分佈等。
假如我們在製作一個遊戲,要隨機設定許許多多 NPC 的身高,如果還用Math.random(),生成從140 到 220 之間的數字,就會發現每個身高段的人數是一樣多的,這是比較無趣的,這樣的世界也與我們習慣不同,現實應該是特別高和特別矮的都很少,處於中間的人數最多,這就要求隨機函式符合正態分佈。
二、正態分佈複習
具體性質也請查閱上面連結,描述正態分佈的主要特徵是均值和方差,如上圖,最左的倒鐘形圖的均值為-2, 其餘為0 ;
方差越大,鐘形越扁平,方差越小越陡;
- 密度函式影象關於均值對稱。
- 在x=μ±σ處,曲線有拐點。
- 函式曲線下68.26%的面積在平均數左右的一個標準差σ的區間內。
- 95.44%的面積在平均數左右兩個標準差2σ的區間內。
- 99.74%的面積在平均數左右三個標準差3σ的區間內。
當均值為0, 方差為 1 時稱為標準正態分佈;
三、由均勻分佈經 “Box-Muller法” 轉換為正態分佈
y1 = sqrt( - 2 ln(u) ) cos( 2 pi v )
y2 = sqrt( - 2 ln(u) ) sin( 2 pi v )
因為三角函式計算較慢,我們可以通過上述公式的一個 polar form
演算法描述如下:
function getNumberInNormalDistribution(mean,std_dev){ return mean+(randomNormalDistribution()*std_dev); } function randomNormalDistribution(){ var u=0.0, v=0.0, w=0.0, c=0.0; do{ //獲得兩個(-1,1)的獨立隨機變數 u=Math.random()*2-1.0; v=Math.random()*2-1.0; w=u*u+v*v; }while(w==0.0||w>=1.0) //這裡就是 Box-Muller轉換 c=Math.sqrt((-2*Math.log(w))/w); //返回2個標準正態分佈的隨機數,封裝進一個數組返回 //當然,因為這個函式執行較快,也可以扔掉一個 //return [u*c,v*c]; return u*c; }
因此,假如我們要獲得均值為180,要68.26%左右的NPC身高都在[170,190]之內,即1個標準差範圍內,因此標準差為10, 可以通過getNumberInNormalDistribution(180,10) 呼叫,我們實驗1000000詞,得到結果如下:
// 身高:頻率 128:1 132:1 133:1 134:1 135:1 136:2 137:4 138:8 139:11 140:14 141:19 142:28 143:41 144:54 145:80 146:133 147:153 148:235 149:333 150:429 151:598 152:764 153:1059 154:1314 155:1776 156:2290 157:2835 158:3503 159:4373 160:5513 161:6475 162:7809 163:9437 164:11189 165:13282 166:15020 167:17239 168:19215 169:21597 170:24336 171:26684 172:29000 173:31413 174:33179 175:35027 176:37084 177:38047 178:38968 179:39635 180:39700 181:39548 182:38960 183:38674 184:36948 185:35220 186:33224 187:31038 188:29198 189:26668 190:23893 191:21662 192:19476 193:16898 194:15056 195:13046 196:10971 197:9456 198:7928 199:6697 200:5370 201:4334 202:3548 203:2810 204:2330 205:1765 206:1350 207:1093 208:797 209:595 210:371 211:328 212:255 213:165 214:121 215:91 216:71 217:29 218:32 219:28 220:20 221:6 222:7 223:7 224:3 225:2 228:1
繪製成柱狀圖如下:
可見,這是有著非常明顯的正態分佈圖像特徵。
四、由均勻分佈疊加獲得正態分佈
我們需要祭出萬能的中心極限定理。
根據獨立同分布的中心極限定理:設隨機變數X1,X2,…Xn,…相互獨立,服從同一分佈,且數學期望為μ,標準差為σ (σ>0),則隨機變數之和的標準化變數:
Y=((X1+X2+…+Xn)-nμ)/(sqrt(n)*sqrt(σ)) 近似服從標準正態分佈 N(0,1)
如果我們將足夠多個均勻分佈隨機變數相加,相加之和將服從正態分佈。但是,我們需要累加多少個均勻分佈才能較好低近似正態分佈呢?
由於 X~U(0, 1) , 可得 μ=1/2, σ=sqrt(1/12),代入上面的式子即可近似模擬隨機變數之和的概率密度函式(p.d.f).
下圖是由2個服從 U(0,1) 分佈的隨機變數相加得到的 p.d.f 影象:
如果我們增加累加的均勻分佈的數量會怎樣呢?
上圖是 n=3 時的影象,可以看到正態分佈的形狀出來了,但頂端還略為平緩。
特別低,當n=12時 (隨機變數(X1+X2+…+Xn)的均值為6,方差為1) 這時有一個很好的特點,公式 Y=((X1+X2+…+Xn)-nμ)/(sqrt(n)*sqrt(σ)) 的分母正好為1,因此簡化成了 Y=((X1+X2+…+Xn)-nμ),非常便於程式設計計算,並且已經非常接近於標準正態分佈,請見下圖:
也就是說均值為μ,標準差為σ 的獨立同分布變數 X1,X2, …, Xn 的算數平均數 T=(X1+X2+ …+ Xn)/n,當n充分大時,近似地服從均值為μ,方差為σ*σ/n 的正態分佈。
最後,程式碼如下:
function getNumberInNormalDistribution(mean,std_dev){ return mean+(uniform2NormalDistribution()*std_dev); } function uniform2NormalDistribution(){ var sum=0.0; for(var i=0; i<12; i++){ sum=sum+Math.random(); } return sum-6.0; }
同樣,將產生100萬個隨機數按頻率畫出直方圖如下: