我需要在一个带有budget
元素的小数组中随机分布一个大整数n
,这样数组中的所有元素都将具有相同的分布并总计为budget
和数组中的每个元素至少得到min
。
我有一个在O(预算)中运行的算法:
private int[] distribute(int budget, int n, int min) {
int[] subBudgets = new int[n];
for (int i = 0; i < n; i++) {
subBudgets[i] = min;
}
budget -= n * min;
while (budget > 0) {
subBudgets[random.nextInt(n)]++;
budget--;
}
return subBudgets;
}
然而,当budget
增加时,它可能非常昂贵。是否有任何算法在O(n)中运行甚至更好?
答案 0 :(得分:3)
首先生成n
个随机数x[i]
,将它们相加,然后将budget
除以总和,然后得到k
。然后将k*x[i]
分配给每个数组元素。这很简单,O(n)。
如果您希望每个元素中至少有min
值,则可以通过min
填充所有元素(或使用k*x[i] + min
)并从中转包n*min
来修改上述算法在开始上述算法之前budget
。
如果您需要使用整数,则可以使用实数值k
并舍入k*x[i]
来解决问题。然后,您必须跟踪累积舍入误差,并在计算值到达整个单位时加上或减去累计误差。您还必须将剩余值分配到最后一个元素中,以达到整个budget
。
P.S。:请注意,此算法可以与简单的纯函数语言一起使用。这就是为什么我喜欢这整个算法族为每个成员生成随机数然后再做一些处理的原因。 Erlang中的实现示例:
-module(budget).
-export([distribute/2, distribute/3]).
distribute(Budget, N) ->
distribute(Budget, N, 0).
distribute(Budget, N, Min) when
is_integer(Budget), is_integer(N), N > 0,
is_integer(Min), Min >= 0, Budget >= N*Min ->
Xs = [random:uniform() || _ <- lists:seq(1,N) ],
Rest = Budget - N*Min,
K = Rest / lists:sum(Xs),
F = fun(X, {Bgt, Err, Acc}) ->
Y = X*K + Err,
Z = round(Y),
{Bgt - Z, Y - Z, [Z + Min | Acc]}
end,
{Bgt, _, T} = lists:foldl(F, {Rest, 0.0, []}, tl(Xs)),
[Bgt + Min | T].
C ++中的相同算法(??我不知道。)
private int[] distribute(int budget, int n, int min) {
int[] subBudgets = new int[n];
double[] rands = new double[n];
double k, err = 0, sum = 0;
budget -= n * min;
for (int i = 0; i < n; i++) {
rands[i] = random.nextDouble();
sum += rands[i];
}
k = (double)budget/sum;
for (int i = 1; i < n; i++) {
double y = k*rands[i] + err;
int z = floor(y+0.5);
subBudgets[i] = min + z;
budget -= z;
err = y - z;
}
subBudgets[0] = min + budget;
return subBudgets;
}
答案 1 :(得分:1)
整合@Hynek -Pichi- Vychodil的想法和我的原始算法,我提出了以下算法,该算法在O(n)中运行,所有舍入误差均匀分布到数组中:
private int[] distribute(int budget, int n, int min) {
int[] subBudgets = new int[n];
for (int i = 0; i < n; i++) {
subBudgets[i] = min;
}
budget -= n * min;
if (budget > 3 * n) {
double[] rands = new double[n];
double sum = 0;
for (int i = 0; i < n; i++) {
rands[i] = random.nextDouble();
sum += rands[i];
}
for (int i =0; i < n; i++) {
double additionalBudget = budget / sum * rands[i];
subBudgets[i] += additionalBudget;
budget -= additionalBudget;
}
}
while (budget > 0) {
subBudgets[random.nextInt(n)]++;
budget--;
}
return subBudgets;
}
答案 2 :(得分:0)
您目前正在分配min
每个子预算后剩余的美元的方式涉及执行固定数量的budget
随机“试用”,每次试用时您随机选择一个n
个类别,您想知道每个类别的选择次数。这是由multinomial distribution建模的,其中包含以下参数:
n
):budget
k
):n
i
类别的可能性,适用于1 <= i <= n
:1/n
如果试验次数与类别数量相同或更少,那么您目前的做法是一种好方法。但是,如果预算很大,那么从这种分布中采用其他更有效的抽样方法。我所知道的最简单的方法是注意到,通过将类别分组在一起,可以将具有k
类别的多项分布重复分解为二项分布:而不是直接为每个k
类别选择多少个选项,我们将此表达为一系列问题:“如何在第一类和另一类k-1
之间拆分预算?”接下来我们问“如何在第二类和另一类k-2
之间拆分余数?”等等。
因此,顶级二项式具有类别(子预算)1与其他所有类别。通过从参数为n = budget
和p = 1/n
的二项分布中提取1个样本来确定预扣1的美元数量(如何对此进行描述here);这将产生一些数字0 <= x[1] <= n
。要查找转到预算2的美元数,请从剩余资金的二项分布中获取1个样本,即使用参数n = budget - x[1]
和p = 1/(n-1)
。获得次级预算2的金额x [2]后,将使用参数n = budget - x[1] - x[2]
和p = 1/(n-2)
找到子预算3,依此类推。
答案 3 :(得分:0)
让我用一个例子演示我的算法:
假设budget = 100, n = 5, min = 10
将数组初始化为:
[10, 10, 10, 10, 10]
=&gt; current sum = 50
生成从0
到50
的随机整数(50
的结果为budget - current sum
):
假设随机整数为20
并更新数组:
[30, 10, 10, 10, 10]
=&gt; current sum = 70
生成从0
到30
的随机整数(30
的结果为budget - current sum
):
假设随机整数为5
并更新数组:
[30, 15, 10, 10, 10]
=&gt; current sum = 75
重复上面的过程,最后一个元素就是剩下的。
最后,将数组洗牌以获得最终结果。