【问题标题】:Distribute a large value among a small number of elements在少量元素中分配大值
【发布时间】:2013-10-31 23:26:32
【问题描述】:

我需要在一个包含n元素的小数组中随机分配一个大整数budget,这样数组中的所有元素将具有相同的分布,并且总和为budget,并且数组中的每个元素都得到至少min

我有一个在 O(budget) 中运行的算法:

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) 甚至更好的时间内运行吗?

【问题讨论】:

  • 您想要实现的随机分布是什么?随机分布有很多种,有些很容易应用于O(n) 中的案例,有些则不然。
  • @RBarryYoung:他已经隐含地提供了该信息:他希望通过将单个美元随机分配到每个预算直到它们全部消失来获得分配。这将是具有budget 试验、n 类别的多项分布,并且每个类别的概率为1/n
  • @j_random_hacker 不,这是你的假设,他从未说过。他所说的只是“distribute..randomly”,这是尽可能模棱两可的,根据我的经验,这通常意味着他们没有真正考虑过不同类型的随机分布的含义。如果这真的是 OP 想要的,那么他所要做的就是说出来,但对于不知道其名称的人来说,这是一个相当奇怪但特定的分布。
  • 抱歉我的模棱两可。我已对其进行了更新,以明确声明 n 个元素具有相同的分布。
  • @RBarryYoung:不,他通过他提供的算法隐含地给出了他想要的分布。该算法从具有我描述的参数的多项分布中采样——通过提供该代码 sn-p,他消除了任何潜在的歧义。此外,我不认为多项分布是“奇怪的”——它肯定是更常见的分布之一,尽管很多使用它的人不知道它的名字。

标签: arrays algorithm random


【解决方案1】:

首先生成n随机数x[i],将它们相加,然后将budget除以和得到k。然后将k*x[i] 分配给每个数组元素。它很简单而且 O(n)。

如果您希望每个元素中至少有min 值,您可以修改上述算法,方法是在开始上述算法之前用min(或使用k*x[i] + min)填充所有元素并从budget 分包n*min

如果您需要使用整数,您可以使用实数 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;
}

【讨论】:

  • 看来 OP 希望每个元素都是整数。我不确定你的回答能否保证这一点。
  • 是的,但是您可以修改算法来实现它。我让它为请求者解决这个问题。
  • 该算法将前一个元素的错误添加到下一个元素。它会使第一个元素在统计上比其他元素少吗?
  • @Clive:绝对不是。所有数字都是相同的随机数,err 对于每个数组成员在 (-0.5,0.5) 区间内的概率是一致的,所以绝对不是。这是完全随机的。
【解决方案2】:

结合@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;
}

【讨论】:

  • 每当min &gt; 0: 你的第三个for 循环从budget 中减去subBudgets[i] 时都会出现错误,忽略subBudgets[i] 包含的min 比@ 的四舍五入值多的事实987654328@.
  • 好收获!谢谢!我已将其固定到位。
  • @Clive 查看我的答案以获得更简单的实现。
【解决方案3】:

让我用一个例子来演示我的算法:

假设budget = 100, n = 5, min = 10

将数组初始化为:

[10, 10, 10, 10, 10] => current sum = 50

生成一个从050的随机整数(50budget - current sum的结果):

假设随机整数为20 并更新数组:

[30, 10, 10, 10, 10] => current sum = 70

生成一个从030的随机整数(30budget - current sum的结果):

假设随机整数为5 并更新数组:

[30, 15, 10, 10, 10] => current sum = 75

重复上面的过程,最后一个元素就是剩下的。

最后,打乱数组得到最终结果。

【讨论】:

  • 数组中的所有元素在您的算法中没有相同的分布。最后一个元素除了min之外几乎没有机会获得任何额外奖励
  • @Clive 它适用于一组。在数组的情况下,您可能需要对数组进行洗牌才能得到结果。
  • @TerryLi:这可能仍然是一种不错的方法,但它肯定会提供与 OP 当前代码不同的分布,即使您将数字随机排列。这是因为选择向某个元素添加 50 的可能性与添加 5 的可能性相同,当 OP 的代码更可能添加一个接近预算/n 的数字而不是远离此的数字时。
【解决方案4】:

从多项分布中抽样

您当前将min 分配给每个子预算后剩余的美元的方式包括执行固定数量的budget 随机“试验”,在每次试验中,您随机选择n 之一类别,并且您想知道每个类别被选择了多少次。这是由具有以下参数的multinomial distribution 建模的:

  • 试验次数(在 WP 页面上称为 n):budget
  • 类别数(在 WP 页面上称为 k):n
  • 每个试验中类别i 的概率,对于1 &lt;= i &lt;= n1/n

如果试验数量与类别数量大致相同或更少,那么您目前的做法是一个好方法。但是如果预算很大,还有其他更有效的方法可以从这个分布中抽样。我所知道的最简单的方法是注意到具有k 类别的多项分布可以通过将类别组合在一起来重复分解为二项分布:而不是直接针对每个k 类别有多少选择,我们表达了这一点作为一系列问题:“如何在第一类和其他k-1之间分配预算?”我们接下来问“如何在第二个类别和另一个k-2之间拆分剩余部分?”等等。

因此,顶级二项式具有类别(子预算)1 与其他所有项。通过从参数为n = budgetp = 1/n 的二项分布中抽取1 个样本来确定进入子预算1 的美元数量(如何做到这一点在here 中有描述);这将产生一些数字0 &lt;= x[1] &lt;= 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。

【讨论】:

  • 感谢您的回答,但我觉得这种方法很难理解/实施。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-02-16
  • 2015-05-24
  • 1970-01-01
  • 2021-12-27
  • 1970-01-01
  • 2018-12-31
  • 2014-05-20
相关资源
最近更新 更多