分屢次累計隨機出某指定整數(屢次隨機整數,其和固定)的方法
Spads
Shane Loo Li
摘要
本文描述了同過 n 次取隨機整數,使其總和爲 m 的方法,並對該方法給出了數學證實。
正文
本文分爲 5 個部分
---------- ---------- ---------- ----------
一、提出問題
二、解法程序
三、測試結果
四、測試程序
五、公式證實
【提出問題】
---------- ---------- ---------- ----------
有 n 次機會,每次隨機一個整數。但願這 n 個整數之和是 m ;該怎麼隨機呢?
對於編程語言,慣例是提供了隨機函數 r() ,獲得 [0, 1) 之間的一個隨機浮點數。因此從編程角度來講,隨機一個整數,最多見的方式就是經過對 L * r() 向下取整來獲取某一個範圍內的整數。以上問題就轉變成爲了,如何得到合適的 L ,來知足 n 次隨機的整數之和爲 m 。
傳統的作法,就是用 2 * m / n 來作這個 L 。問題是由於取整這個操做,讓這種算法會產生比較大的偏差。具體偏差有多大,下邊測試結果一欄會詳細描述。
【解法程序】 —— 以 Java 程序爲例
---------- ---------- ---------- ----------
/**
* <b>獲取總量固定屢次隨機的倍率</b><br/>
* 當程序須要經過必定次數隨機,每次隨機一個整數,最終獲取總和必定的值,
* 可經過此方法得到隨機倍率。<br/>
* 在獲取此倍率 <code>randomLimit</code> 以後,每次隨機時經過
* <code>(int) (new Random().nextDouble() * randomLimit)</code> 得到隨機
* 結果。<br/><br/>
* 本方法的核心算法,基於證實了以下二個關係式:
* <pre>
* (int) (2 * totalNum / chanceCount) + 1 < randomLimit
* (int) (2 * totalNum / chanceCount) + 2 > randomLimit
* </pre>
* 具體推算方法,請見 Spads 的 Shane Loo Li 發表的日誌。<br/>
* http://blog.csdn.net/shanelooli/article/details/10831811
* @param totalNum 最終但願各隨機值相加後的總量
* @param chanceCount 隨機次數
* @return 每次隨機,[0, 1) 標準隨機值應該乘以的倍率
*/
static public double getRandomLimit(int totalNum, int chanceCount)
{
double calculateBase = 2.0 * totalNum / chanceCount;
int calculateBaseInt = (int) calculateBase;
double randomLimit = (calculateBaseInt + 2) * (calculateBaseInt + 1)
/ (2 * calculateBaseInt - calculateBase + 2);
return randomLimit;
}
【測試結果】
---------- ---------- ---------- ----------
目標總和爲 5000000
隨機次數 20000
簡易方法: 實際隨機數的總和 = 4993051, 偏差 = 0.1389%
Spads Shane的新方法: 實際隨機數的總和 = 4997315, 偏差 = 0.0537%
隨機次數 50000
簡易方法: 實際隨機數的總和 = 4946686, 偏差 = 1.10661%
Spads Shane的新方法: 實際隨機數的總和 = 4992772, 偏差 = 0.1445%
隨機次數 150000
簡易方法: 實際隨機數的總和 = 4915701, 偏差 = 1.16858%
Spads Shane的新方法: 實際隨機數的總和 = 5003719, 偏差 = 0.0743%
隨機次數 500000
簡易方法: 實際隨機數的總和 = 4758806,
偏差 = 4.48234%
Spads Shane的新方法: 實際隨機數的總和 = 4997350, 偏差 = 0.0530%
隨機次數 1000000
簡易方法: 實際隨機數的總和 = 4502306,
偏差 = 9.99529%
Spads Shane的新方法: 實際隨機數的總和 = 5003257, 偏差 = 0.0651%
隨機次數 3000000
簡易方法: 實際隨機數的總和 = 3602468,
偏差 = 27.279479%
Spads Shane的新方法: 實際隨機數的總和 = 4999469, 偏差 = 0.0106%
隨機次數 5000000
簡易方法: 實際隨機數的總和 = 2500655,
偏差 = 49.499820%
Spads Shane的新方法: 實際隨機數的總和 = 5000972, 偏差 = 0.0194%
隨機次數 8000000
簡易方法: 實際隨機數的總和 = 1598759,
偏差 = 68.680180%
Spads Shane的新方法: 實際隨機數的總和 = 4996882, 偏差 = 0.0623%
隨機次數 13000000
簡易方法: 實際隨機數的總和 = 0,
偏差 = 100.0000%
Spads Shane的新方法: 實際隨機數的總和 = 4999994, 偏差 = 0.0001%
隨機次數 20000000
簡易方法: 實際隨機數的總和 = 0,
偏差 = 100.0000%
Spads Shane的新方法: 實際隨機數的總和 = 4995380, 偏差 = 0.0924%
能夠見到,不管多少次隨機,簡易方法比 Spads Shane 的新方法偏差都要大。當隨機次數較多時,簡易方法偏差顯著增長;若是隨機次數和最終所需的和達到同一個數量級,簡易方法的偏差就會極大,使得這種方法沒法再使用。
以上測試結果報告,由下邊給出的測試程序直接生成。
【測試程序】
---------- ---------- ---------- ----------
public void testRandomLimit()
{
// 指定屢次隨機的整數加起來的預期總和,並顯示
int totalNum = 5000000;
System.out.println("目標總和爲 " + totalNum);
// 隨機次數
int[] chanceCounts = {
20000, 50000, 150000, 500000, 1000000, 3000000,
5000000, 8000000, 13000000, 20000000
};
// 二種方法
String[] reportTitles = {"\n簡易方法:\t\t", "\nSpads Shane的新方法:\t"};
double[] randomLimits = new double[2];
// 一個隨機數生成對象便可解決問題,不必重複生成此對象
Random ran = new Random();
// 遍歷各類隨機次數,分別測評
for (int index, methodIndex, chanceIndex = -1;
++chanceIndex != chanceCounts.length; )
{
// 獲取本次測評的隨機次數,並顯示
StringBuilder report = new StringBuilder();
int chanceCount = chanceCounts[chanceIndex];
report.append("\n隨機次數 ").append(chanceCount);
// 分別指定傳統方法的隨機倍率,和 Spads Shane 新方法的隨機倍率
randomLimits[0] = 2.0 * totalNum / chanceCount;
randomLimits[1] = getRandomLimit(totalNum, chanceCount);
// 分別測評二種方法,最終顯示結果報告
for (methodIndex = -1; ++methodIndex != 2; )
{
report.append(reportTitles[methodIndex]);
int realCount = 0;
double randomLimit = randomLimits[methodIndex];
for (index = -1; ++index != chanceCount; )
realCount += (int) (ran.nextDouble() * randomLimit);
report.append("實際隨機數的總和 = ").append(realCount);
double error = Math.abs(realCount - totalNum) / (double) totalNum;
int percent = (int) (error * 100);
report.append(", 偏差 = ").append(percent).append('.');
String decimalStr = "0000";
if (percent != 100)
{
decimalStr = String.valueOf((int) (error * 1000000) - percent);
if (decimalStr.length() < 4)
report.append("0000".substring(decimalStr.length()));
}
report.append(decimalStr).append('%');
}
System.out.println(report.toString());
}
}
【公式證實】
---------- ---------- ---------- ----------
以前咱們看到,程序以一個並不太複雜的四則運算式,經過目標總和 m 與隨機次數 n ,獲得了隨機倍率參量 L 。L 自身將是一個大於 1 的實數。由於若是 L <= 1 ,則 int(L * r()) 恆爲 0 。
設 Z = 2m / n
L = [int(Z) + 1][int(Z) + 2] / [2int(Z) - Z + 2]
其中 int(x) 爲向下取整函數。
接下來,咱們來證實這個公式的正確性。
一、經過幾率核心定律,求得 L 與 m, n 的關係
機率核心定律,認爲有 p 機率發生的事情,在嘗試屢次後,其發生比例趨近於 p 。
設用 L * r() 取隨機數,平均結果爲 R ,可知 R * n = m ,即 2R = Z。
這裏 r() 爲產生 [0, 1) 隨機數的函數。
咱們老是能夠把 L 表示成 a + b ,其中 a = int(L),b∈[0, 1) 。由於 L > 1 ,因此 a >= 1 。
因而可以得到用 L, a 表示的 R 的表達式;其中最重要的一點,就是由於取整這種運算的存在,因此隨機出 a 的機率,要小於其他整數。
R = { ∑(0 * 1/L) + (1 * 1/L) + (2 * 1/L) + ... + [(a - 1) * 1/L] } + a * (L - a)/L
根據等差數列求和公式,求 R 的表達式具體形式
R = [0/L + (a - 1)/L] * a / 2 + a * (L - a) / L
Z = 2R
= [a(a - 1) + 2a(L - a)] / L
= (2aL - a^2 - a) / L
二、證實 a = int(Z) + 1 ,開始的推論
由於 a = L - b ,b∈[0, 1) ,因此爲證實 a = int(Z) + 1 ,只須要證實以下二個關係式:
int(Z) + 1 < L ①
int(Z) + 2 > L ②
爲此,咱們須要用能夠肯定範圍的已知量,來表示 int(Z) 。
咱們老是能夠把 Z 表示成 int(Z) + c ,其中 c∈[0, 1) 。
根據以前的推論,咱們知道 Z = (2aL - a^2 - a) / L
將 L = a + b ,b∈[0, 1) 代入上式
Z = [2a(a + b) - a^2 - a] / (a + b)
= (a^2 + 2ab - a) / (a + b)
= [(a + b)^2 - b^2 - a] / (a + b)
= a + b - (a + b^2) / (a + b)
至此,咱們看到 Z = int(Z) + c = a + b - (a + b^2) / (a + b)
若是 b - (a + b^2) / (a + b) 是一個可以把絕對值範圍控制在 1 之內的量,就能夠經過取整原則,求得用 a, b 表示的 c 的表達式。
三、證實 b - (a + b^2) / (a + b) ∈ [-1, 0)
先證實
b - (a + b^2) / (a + b) < 0
↑
b < (a + b^2) / (a + b)
↑ ∵ a + b = L > 1 > 0
b(a + b) < a + b^2
↑
ab + b^2 < a + b^2
↑
ab < a
↑ ∵ a >= 1 > 0
b < 1
原題得證
再證實
b - (a + b^2) / (a + b) >= -1
↑
(a + b^2) / (a + b) - b <= 1
↑
(a + b^2) / (a + b) <= 1 + b
↑ ∵ a + b = L > 1 > 0
a + b^2 <= (a + b)(1 + b)
↑
a + b^2 <= a + ab + b + b^2
↑
0 <= ab + b
原題得證
所以,可知 b - (a + b^2) / (a + b) ∈ [-1, 0)
四、證實 a = int(Z) + 1 ,原題得證
根據上邊的結論,
Z = int(Z) + c = a + b - (a + b^2) / (a + b)
int(Z) + c = a - 1 + [1 + b - (a + b^2) / (a + b)]
由於 b - (a + b^2) / (a + b) ∈ [-1, 0)
因此 1 + b - (a + b^2) / (a + b) ∈ [0, 1)
證實,若是 A + B = C + D ,A, C 爲整數,B, D∈[0, 1) ,那麼 B = D
∵ A + B = C + D
∴ A - C = D - B
若 A - C = 0 ,則 B = D
若 A - C >= 1 ,則 D - B = A - C >= 1
↑
D >= 1 + B
∵ D < 1 且 B >= 0
∴ D >= 1 + B 不成立。
∴ A - C >= 1 不成立。
∴ B = D 且 A = C
所以可知 c = 1 + b - (a + b^2) / (a + b) 且 int(Z) = a - 1
a = int(Z) + 1
五、求得 L 用 Z 的表達式
Z = (2aL - a^2 - a) / L
ZL = 2int(Z)L + 2L - [int(Z) + 1]^2 - int(Z) - 1
ZL - 2L - 2int(Z)L = -1 - [int(Z) + 1]^2 - int(Z)
L * [2 + 2int(Z) - Z] = [int(Z) + 1]^2 + int(Z) + 1
L = [int(Z)^2 + 2int(Z) + 1 + int(Z) + 1] / [2 + 2int(Z) - Z]
L = [int(Z)^2 + 3int(Z) + 2] / [2int(Z) - Z + 2]
L = [int(Z) + 1][int(Z) + 2] / [2int(Z) - Z + 2]
本文還發表於在其它網站
CSDN :
http://blog.csdn.net/shanelooli/article/details/10831811
ITeye :
http://surmounting.iteye.com/blog/1935022
51CTO :
http://shanelooli.blog.51cto.com/5523233/1286679