如何生成N个随机值,使它们的总和为预定值?

23

我需要您的帮助解决一个小问题。我有四个标签,希望在它们上面显示0到100之间的随机值,并且它们的和必须为100。

这是我的代码:

private void randomly_Click(object sender, EventArgs e)
{
    double alpha = 0, beta = 0, gamma = 0, delta = 0;
    double temp;
    int tempDouble;

    Random rnd = new Random();

    alpha = rnd.Next(0, 100);

    temp = 100 - alpha;
    tempDouble = (int)temp;
    beta = rnd.Next(0, tempDouble);

    temp = 100 - (alpha + beta);
    tempDouble = (int)temp;
    gamma = rnd.Next(0, tempDouble);

    temp = 100 - (alpha + beta + gamma);
    tempDouble = (int)temp;
    delta = rnd.Next(0, tempDouble);

    temp = alpha + beta + delta + gamma;
    temp = 100 - temp;
    temp = temp / 4;

    alpha = alpha + temp;
    beta = beta + temp;
    gamma = gamma + temp;
    delta = delta + temp;

    cInsertion.Text = alpha.ToString();
    cMoyens.Text = beta.ToString();
    cInternational.Text = gamma.ToString();
    cRecherche.Text = delta.ToString();
}   
问题在于我给了alpha有最大的价值,而给delta有最小的价值。有没有办法让它们都有相同的机会获得真正的随机值?

一个真正的随机数?简而言之,没有。只有伪随机数。随机数生成器依赖于时钟周期运作,通常会出现重复序列。使用的种子取决于当前 DateTime.Now 的 .Tick 属性(如果我记得正确的话)。对于 .Net,有很多不错的随机数生成器和 API,并且如果你觉得自己没有时间或者没有兴趣去开发自己的随机数生成器,我建议你去搜索一下这些工具。 - IAbstract
6个回答

46

你可以像这样做:

double alpha = 0, beta = 0, gamma = 0, delta = 0, k = 0;
Random rnd = new Random();

alpha = rnd.Next(0, 100);
beta = rnd.Next(0, 100);
gamma = rnd.Next(0, 100);
delta = rnd.Next(0, 100);

k = (alpha + beta + gamma + delta) / 100;

alpha /= k;
beta /= k;
gamma /= k;
delta /= k;

cInsertion.Text = alpha.ToString();
cMoyens.Text = beta.ToString();
cInternational.Text = gamma.ToString();
cRecherche.Text = delta.ToString();

你的意思是先为所有4个变量选择一个随机值,然后通过因子 k 进行缩放,以使它们的总和为100。


6
如果将这四个值都放入一个数组中,然后对它们进行洗牌,再根据它们的顺序分配,那么每个变量(gamma、theta等等)获得高或低数字的概率就相等了。

1
这会使得所有四个值具有相同的分布,但是这是一种非常奇怪的分布。 - Ben Voigt

0

这是一个非常有趣的问题。我喜欢@Ivan_Ferić的解决方案,我认为它很完美,但我有另一个想法:

int total = 100;
Random rand = new Random();

int half = rand.next(0,total);   // the line

a = rand.Next(0,half);
b = max - a;
c = rand.Next(0,half);
d = max - c;

(未经测试,某些变量可能需要添加+/—1。)此外,您可以随机排列值。

有趣的部分现在开始了。如果我们改变“行”:

min = <some_value>
max = <another_value>;
int half = rand.next(min,max);

...然后我们可以微调结果:

  • min = 0; max = 100:原始值;
  • min = 20; max = 80:避免小数值;
  • min = 33; max = 100:强制两个数字小于32。

即使min=0,max=100,这也不同于Ivan的解决方案,因为如果一半小于5(例如发生了5%的情况),那么两个数字的总和将小于5。而在Ivan的解决方案中,要发生这种情况,必须有两个数字小于5,另外两个数字大于50,这种情况只会发生3/8的百分比。 - Neil G

0

不要随机生成单个数字,而是生成部分和,然后计算部分:

double alpha, beta, gama, delta = 0;

var rnd = new System.Random();
var cuts = new List<double>();

cuts.Add(Math.Floor(rnd.Next(0, 101)));
cuts.Add(Math.Floor(rnd.Next(0, 101)));
cuts.Add(Math.Floor(rnd.Next(0, 101)));

cuts.Sort();

alpha = cuts[0];
beta = cuts[1] - cuts[0];
gamma = cuts[2] - cuts[1];
delta = 100 - cuts[2];

+1 这个为什么会被踩呢?实际上这是一个很好的解决方案,不需要依赖除法/乘法。 - Philip Daubmeier
1
感谢@Philip。人们通常会期望在研究生水平的概率和统计课程中学到的技巧能够与即兴解决方案相媲美,但我想天真的选民更喜欢他们自己提出的天真解决方案。 - Ben Voigt
我非常怀疑alpha和delta与beta和gamma具有相同的分布,不像Ivan的情况。 - gnasher729

0
如果你想要的数字代表互斥结果的概率,你可以将它们想象成多项式分布。由于你想要一个随机概率向量,所以你应该从多项式分布的共轭先验——狄利克雷分布中进行采样。
在代码中,你只需要像Ivan建议的那样,除了alpha、beta、gamma和delta应该分别从具有形状参数alpha_shape、beta_shape、gamma_shape和delta_shape的Gamma分布中进行抽取。(让Gamma分布的另一个参数为1。)这些形状参数控制了预期相对比例。

-2

一个真正的随机数?简而言之,没有。只有伪随机数。

随机数生成器是基于时钟周期运作的,通常会看到重复的序列。所使用的种子取决于当前DateTime.Now的.Tick属性(如果我没记错的话)。对于.Net,有很多不错的随机数生成器和API。如果你觉得没有时间或者没有兴趣自己编写,我建议你去搜索一下这些。

由于评论更为合适,已移至评论区。


标准 P-RNG 的问题在于,由于时钟的循环性质,它会以极小的变化执行。 - IAbstract
@finnw:不是这样的:“有没有办法让它们都有同等机会获得真正的随机值。” - IAbstract
@finnw:最初,当我回答时,他的标题是:“生成随机值”。 - IAbstract
@IAbstract,我相当确定OP的意思是“所有四个变量是否可以具有相同的分布”(是)或“如果独立考虑,所有四个变量是否可以均匀分布?”(不行,但这并不是由于PRNG的确定性造成的。) - finnw
你可以点击这个答案上的小“删除”链接,以恢复因负评而失去的声望值。 - Timwi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接