【问题标题】:java random percentagesjava随机百分比
【发布时间】:2010-06-09 16:51:45
【问题描述】:

我需要生成 n 个百分比(0 到 100 之间的整数),使得所有 n 个数字的总和为 100。

如果我只做nextInt() n 次,每次确保参数是 100 减去之前累积的总和,那么我的百分比是有偏差的(即第一个生成的数字通常是最大的等等.)。我该如何以公正的方式做到这一点?

【问题讨论】:

  • 有趣的问题,但我认为答案不会是特定于 Java 的。
  • 您可以随机生成,直到总和超过 100,然后“封顶”最终数字。这样,除了最终数字之外的所有数字都是随机的。我不明白您如何“随机”得出由非随机约束定义的预设总和。
  • 随机化你做它们的顺序。假设你有 5 个数字,那么你可能先做 #3,然后下一次 #4,下一个 #1 等等......
  • 我认为如果不首先考虑您期望这些随机数的分布是什么样子,就无法回答这个问题。如果您想要“正态分布”(钟形曲线),那么@LanceH 的答案应该有效。如果您期望“均匀分布”,我怀疑这是不可能的。您想要什么样的分布完全决定了解决方案的性质。
  • @Kevin。如果人们忽略概率分布,我会说假设均匀是合理的。而且,获得均匀分布是很有可能的,为什么说不是呢?

标签: java algorithm math random


【解决方案1】:

一些答案​​建议选择随机百分比并获取它们之间的差异。正如 Nikita Ryback 指出的那样,这不会给出所有可能性的均匀分布;特别是,零的频率将低于预期。

要解决此问题,请考虑从 100“百分比”开始并插入分隔线。我将展示一个 10 的示例:

 % % % % % % % % % 

我们可以在十一个地方插入分隔符:在任意两个百分比之间或开头或结尾。所以插入一个:

 % % % % / % % % % % % 

这代表选择四和六。现在插入另一个分隔线。这一次,有十二个地方,因为已经插入的分隔线创建了一个额外的地方。具体有两种获取方式

 % % % % // % % % % % % 

在前一个分隔符之前或之后插入。您可以继续该过程,直到您拥有所需数量的分隔符(比百分比数少一个)。

 % % / % / % / / % % % / % % % / 

这对应于 2,1,1,0,3,3,0。

我们可以证明这给出了均匀分布。 100的组合数分成k份就是二项式系数100+k-1选k-1。那是 (100+k-1)(100+k-2)...101 / (k-1)(k-2)*...*2* 1 因此,选择任何特定组合的概率是这个的倒数。当我们一次插入一个分隔符时,首先我们从 101 个位置中进行选择,然后是 102、103 等,直到达到 100+k-1。所以任何特定插入序列的概率是 1 / (100+k-1)*...*101。多少个插入序列产生相同的组成?最终组合包含 k-1 个分隔符。它们可以按任何顺序插入,所以有 (k-1)!产生给定构图的序列。所以任何特定组合的概率正是它应该是的。

在实际代码中,您可能不会这样表示您的步骤。您应该能够只保留数字,而不是百分比和分隔符的序列。这个算法的复杂度我还没想过。

【讨论】:

  • +1 这应该等同于随机选择一个已经抽出(有多重性)的数字或一个新的。
  • 这不是因为可以选择分隔线之前或之后的空间。本质上,有两种方法可以选择已经抽取的数字。 (如果该数字已被抽出 n 次,则为 n+1。)
  • 哦,如果您的意思是从已抽出的号码列表中选择或从 0-100 中选择任何号码(不一定是新号码),那么它们是相同的。您只需要确保对任何选择都给予相同的概率。
  • 是的,就是这样。
  • 这对我来说有些可疑,尽管我并不深入了解它。对于给定的结果,有多个可能的分隔符插入序列会导致它。当结果不包含零时,只有 n!获得该结果的方法(对应于可以放置分隔符的不同顺序)。但是当有零时,重复的次数比这多……或者有吗?组合学很难。
【解决方案2】:

生成 n 个任意范围的随机整数(称它们为 a[1]..a[n])。总结你的整数并称之为b。您的百​​分比将为[a[1]/b, ..., a[n]/b]

编辑:好点,将结果四舍五入到总计 100 是不平凡的。一种方法是在1..n 中为xa[x]/b 作为整数,然后随机分配剩余的单位100-(sum of integers)。我不确定这是否会给结果带来任何偏见。

【讨论】:

  • 唯一的问题是a[0]/b可能不是整数。
  • @Nikita:那又怎样?只需四舍五入,这样在四舍五入后总和 = 100。
  • 这似乎是一个不错的解决方案。假设比率结果不是整数,那么我可以(随机)选择这些数字并将总和与 100 之间的差添加到其中吗?这似乎可以最大限度地减少分布偏差。
  • @Ben 修修补补会使分布不那么均匀(某些组合会比其他组合更频繁地发生)。但我同意,如果“任何范围”足够大,它就非常接近了。
  • -1:对不起,这没用,如果做对了,会变得不必要的复杂。我相信 mikera 的解决方案要好得多。
【解决方案3】:

您可能需要定义“有偏见”的真正含义 - 但如果您只关心数字的分布与其位置无关,那么您可以简单地以“有偏见”的方式创建数字并且然后随机化他们的位置。

另一种“不偏不倚”的方法是创建 n-1 个随机百分比,对它们进行排序(称为 x1 x2 x3...),然后将最终百分比定义为:

x1
x2 - x1
x3 - x2
...
100 - x(n-1)

这样你会得到 n 个加到 100 的随机数。

【讨论】:

  • @erw:这个解决方案比目前排名靠前的解决方案(投票数)要好得多!它甚至减少了一个随机数调用,并且不涉及四舍五入等。另外,我认为我们可以证明它以相同的概率生成每个组合。
  • +1 与 Adamski 的解决方案相同,并且与零有相同的问题。但如果所有元素都 > 0,那就完美了。
  • @Nikita:如果您将其视为生成多组 n-1 个数字,那么它会给出无偏分布(我认为,但尚未尝试证明),因为每个分布我们需要正好对应一个多集。正如您所说,我关于少调用一个随机数的说法并不准确,因为一一生成随机数以创建多重集并不是无偏的。事实上,如果 n 足够小,我相信只需 一个 随机数调用就可以做到这一点。请参阅:stackoverflow.com/questions/3003192/…
  • 添加到以前的评论。由于 100 选择 50
  • @Moron 为什么我们要通过一个随机调用来限制自己?我提到的问题(你在争论,对吧?)是他没有正确生成多重集:将有两个“路径”来生成 (1, 2) 多重集(来自序列 [1, 2]和 [2, 1] 并且 (1, 1) 只有一种方式。因此,第一种方式的概率是前者的两倍。
【解决方案4】:

这个问题被称为单纯形的均匀采样,维基百科提供了两种算法:

http://en.wikipedia.org/wiki/Simplex#Random_sampling

另请参阅以下相关问题:

【讨论】:

  • 不幸的是,大多数观众都无法访问维基百科的文章。我需要它变得愚蠢。 :)
  • @Kevin:第一个算法和ataylor's一样,第二个算法和mikera's一样(不幸的是,它遇到了Nikita提出的问题——它在文章中提到有一个已知的问题,但他们没有具体说明什么)。 eruonna's solution 解决了这个问题 - 有趣的是维基百科还不知道它:)
  • @BlueRaja:ataylor 的解决方案不是真的。您必须从指数分布生成,然后进行归一化。
  • @BlueRaja:eruonna 的解决方案与证明集合总数为 100+N-1 的证明具有相同的(很棒的!)想法,但它使用了太多的随机数调用。想象一下生成这样的数字一百万次。类似的想法可以为我们提供只有 一个(或两个)随机数调用 n
【解决方案5】:

关键是要生成 N 个介于 0 和 100 之间的随机数,但要使用这些作为“标记”而不是要输出的最终数字序列。然后按升序遍历标记列表,计算每个百分比以输出为(当前标记 - 前一个标记)。

与简单地一次生成和输出每个数字相比,这将提供更均匀的分布。

示例

import java.util.Random;
import java.util.TreeSet;
import java.util.SortedSet;

public class Main {
  public static void main(String[] args) {
    Random rnd = new Random();
    SortedSet<Integer> set = new TreeSet<Integer>();

    for (int i=0; i<9; ++i) {
      set.add(rnd.nextInt(101));
    }

    if (set.last() < 100) {
      set.add(100);
    }    

    int prev = 0;
    int total = 0;    
    int output;

    for (int j : set) {
      output = j - prev;
      total += output;
      System.err.println(String.format("Value: %d, Output: %d, Total So Far: %d", j, output, total));
      prev = j;
    }
  }
}

输出

$ java Main
Value: 0, Output: 0, Total So Far: 0
Value: 2, Output: 2, Total So Far: 2
Value: 55, Output: 53, Total So Far: 55
Value: 56, Output: 1, Total So Far: 56
Value: 57, Output: 1, Total So Far: 57
Value: 69, Output: 12, Total So Far: 69
Value: 71, Output: 2, Total So Far: 71
Value: 80, Output: 9, Total So Far: 80
Value: 92, Output: 12, Total So Far: 92
Value: 100, Output: 8, Total So Far: 100

【讨论】:

  • 我怀疑分布将与 ataylor 或我的解决方案相同。但是,您的涉及插入 BST,因此 O(NlogN) 而不是 O(N)。您还需要添加最后一个条目以使总和达到 100
  • +1,我喜欢这个主意。每个有效分布都转换为一组 (n - 1) 个标记,因此我们也可以生成标记。代码看起来很困难,但我相信它可以变得更简单:)
  • 不过,它确实存在零问题。将在 n 中生成一组标记 (1, 2, .., n - 1)!案例(考虑顺序),而一组标记 (100, 100, .., 100)(导致分布 100, 0, .., 0)仅在一个案例中生成。
【解决方案6】:

制作一个数组。将 100% 随机放入该数组的每个部分。 示例显示 n=7。

import java.util.Random;

public class random100 {
    public static void main (String [] args) {
        Random rnd = new Random();
            int percents[] = new int[7];
            for (int i = 0; i < 100; i++) {
                int bucket = rnd.nextInt(7);
                percents[bucket] = percents[bucket] + 1;
            }
        for (int i = 0; i < 7; i++) {
            System.out.println("bucket " + i + ": " + percents[i]);
        }

    }

}

【讨论】:

  • Nope :) 您为 n == 2 编写的程序只有一种达到分布 (100, 0) 的方式:每一步 rnd.nextInt == 0。但是有很多方法可以达到 (50, 50):从 50 到 100 的二项式系数。两个分布应该具有相等的概率。
  • rnd.nextInt(2) 产生 0 或 1,似乎工作得很好。我得到 (46, 54), (48, 52), (56,44) 等的结果...
  • @LanceH 请注意您的示例如何聚集在 (50,50) 附近。那些更接近 (50,50) 的发生概率比例如高得多。 (100, 0)。
  • 误读了 Nikita 所写的内容。是的,它倾向于中间,这对于 n=2 看起来特别糟糕。但是在 n=2 的情况下,通过第一个数字的简单随机数和确定第二个数字,解决方案是微不足道的。对于 n=3,没有提到期望。如果所有 3 之间的分布相等,那么单个存储桶的分布在 0 到 100 之间不会是均匀的……这可能意味着 n=2 的情况相同。至少它暗示 n=2 与 n>2 是完全不同的野兽。没有提及预期分布。
  • 完全正确,这个答案是完全有效的,因为没有提到预期的分布。
【解决方案7】:

确切地说,这取决于您希望样本如何不偏不倚。这是一个粗略的方法,大致会给你一个很好的结果。

  1. 从 0,..100 生成 n-1 整数,例如 a[i]i = 0, to n-2
  2. total 是这些数字的总和
  3. i = 0, to n-2 计算b[i] = floor(100*a[i]/total)
  4. 设置b[n-1] = 100 - (b[0] + ... b[n-2])

那么 b 就是你得到的百分比数组。

最后一个会有偏差,但其余的应该是统一的。

当然,如果您想以更准确的方式执行此操作,则必须使用 Gibbs 采样或 Metropolis hastings。

【讨论】:

  • 我认为这是要走的路。如果你能去掉整数限制,你就可以去掉可能会导致一些偏差的舍入,但是生成 100 个数字然后规范化的整个过程似乎是合理的。
【解决方案8】:

使用您描述的方法选择数字后,将数字的顺序打乱。这样,最终的数字列表分布更均匀。

但是,请注意,无论您做什么,都无法获得完美均匀的分布,因为一旦您开始选择数字,您的随机试验就不是独立的。请参阅ataylor 的答案。 p>

另请注意,您描述的算法可能无法为您提供所需的输出。最后一个数字不能是随机的,因为它必须使总和等于 100。

【讨论】:

  • “不独立”!当然!我有这样的感觉,但无法用语言表达。
【解决方案9】:

首先,显而易见的解决方案。

do
    int[] a = new int[n];
    for (int i = 0; i < n; ++i) {
        a[i] = random number between 0 and 100;
    }
until sum(a) == 100;

它在复杂性方面并不完美(达到总和 100 的迭代次数可能非常大),但分布肯定是“无偏的”。

编辑
类似的问题:如何在半径为 1 且中心在 (0, 0) 的圆中生成随机点?解决方案:继续在范围(正方形)[-1..1,-1..1] 中生成随机点,直到其中一个适合圆圈:)

【讨论】:

  • 如果第一个随机数是99,第二个是2怎么办?
  • 如果n=3 和前两个数字之和 > 100 怎么办?
  • @Joachim, @Ben 那么最终总和将大于 100,我们将不得不重复这个过程。
  • 这不起作用 - OP 希望生成特定数量的百分比,但您的代码可能只生成 1(如果生成的第一个随机数是 100)。
  • 哈哈,这相当于 bogosort。该算法是正确的,但需要大量的迭代才能完成。我现在正在计算数字!我希望你的意思是开玩笑:)
【解决方案10】:

我遇到了类似的问题,最终按照你说的做,生成随机整数,直到现有整数之和与限制之差。然后我随机化了整数的顺序。它工作得很好。那是为了遗传算法。

【讨论】:

    【解决方案11】:

    假设您有 100 块石头和 N 个桶来放置它们。您可以将所有 100 块石头放入随机桶中。这样,总数将是您开始时的 100,并且任何存储桶之间都不会存在偏差。

    public static int[] randomBuckets(int total, int n_buckets) {
        int[] buckets = new int[n_buckets];
        Random rand = new Random();
        for(int i=0;i<total;i++)
            buckets[rand.nextInt(n_buckets)]++;
        return buckets;
    }
    
    public static void main(String... args) {
        for(int i=2; i<=10;i++)
            System.out.println(Arrays.toString(randomBuckets(100, i)));
    }
    

    打印

    [55, 45]
    [38, 34, 28]
    [22, 21, 32, 25]
    [28, 24, 18, 15, 15]
    [17, 14, 13, 21, 18, 17]
    [17, 19, 14, 15, 6, 15, 14]
    [11, 14, 14, 14, 4, 17, 9, 17]
    [13, 12, 15, 12, 8, 10, 9, 11, 10]
    [11, 13, 12, 6, 6, 11, 13, 3, 15, 10]
    

    随着计数的增加,分布趋于均匀。

    System.out.println(Arrays.toString(randomBuckets(100000000, 100)));
    

    打印

    [1000076, 1000612, 999600, 999480, 998226, 998303, 1000528, 1000450, 999529, 
    998480, 998903, 1002685, 999230, 1000631, 1001171, 997757, 1000349, 1000527, 
    1002408, 1000852, 1000450, 999318, 999453, 1000099, 1000759, 1000426, 999404, 
    1000758, 1000939, 999950, 1000493, 1001396, 1001007, 999258, 1001709, 1000593,
    1000614, 1000667, 1000168, 999448, 999350, 1000479, 999991, 999778, 1000513, 
    998812, 1001295, 999314, 1000738, 1000211, 999855, 999349, 999842, 999635, 
    999301, 1001707, 998224, 1000577, 999405, 998760, 1000036, 1000110, 1002471, 
    1000234, 1000975, 998688, 999434, 999660, 1001741, 999834, 998855, 1001009, 
    999523, 1000207, 998885, 999598, 998375, 1000319, 1000660, 1001727, 1000546, 
    1000438, 999815, 998121, 1001128, 1000191, 998609, 998535, 999617, 1001895, 
    999230, 998968, 999844, 999392, 999669, 999407, 998380, 1000732, 998778, 1000522]
    

    【讨论】:

    • 根据中心极限定理,这将趋向于围绕中心 (total/2) 作为 n_buckets->infinity 的正态分布。我认为 OP 想要的是让每个可能的组合都具有同样的可能性(即在所有 n 个整数加起来为 100 的集合上均匀分布)。
    • @LL-Bhima 每个桶都有相同的机会增加,你为什么会怀疑除了同样平坦的分布?
    • @Kevin 结果并不完全一致,上一个例子中的最低和最高之间的差异远小于 1%。
    【解决方案12】:

    这是我为正在创建的程序编写的一些代码。我在尝试解决这个确切问题时发现了这个线程,所以希望这对其他人有帮助。该设计基于阅读上面的@eruonna 回复。

    public static int[] randomNumbers(int numOfNumbers){
    
        int percentN = numOfNumbers;
    
        int[] intArray = new int[101];
    
        //set up the array with values
        for(int i = 0; i < intArray.length; i++){
            intArray[i] = i;
        }
    
        //set up an array to hold the selected values
        int[] selectionArray = new int[(percentN - 1)];
    
        //run a for loop to go through and select random numbers from the intArray
        for(int n = 0; n < selectionArray.length; n++){
            int randomNum = (int)(Math.random() * 100);
            selectionArray[n] = intArray[randomNum];
        }
    
        //bubble sort the items in the selectionArray
        for(int out = (selectionArray.length - 1); out > 1; out--){
            for(int in = 0; in < out; in++){
                if(selectionArray[in] > selectionArray[in + 1]){
                    int temp = selectionArray[in];
                    selectionArray[in] = selectionArray[in + 1];
                    selectionArray[in + 1] = temp;
                }
            }
        }
    
        //create an array to hold the calculated differences between each of the values to create random numbers
        int[] calculationArray = new int[percentN];
    
        //calculate the difference between the first item in the array and 0
        calculationArray[0] = (selectionArray[0] - 0);
    
        //calculate the difference between the other items in the array (except for the last value)
        for(int z = 1; z < (calculationArray.length - 1); z++){
            calculationArray[z] = (selectionArray[z] - selectionArray[z - 1]);
        }
    
        //calculate the difference for the last item in the array
        calculationArray[(calculationArray.length - 1)] = (100 - selectionArray[(selectionArray.length - 1)]);
    
        return calculationArray;
    
    }
    

    【讨论】:

      猜你喜欢
      • 2021-01-19
      • 1970-01-01
      • 1970-01-01
      • 2019-09-24
      • 2021-06-22
      • 2021-02-18
      • 2021-12-20
      • 2018-01-31
      • 2016-02-26
      相关资源
      最近更新 更多