生成具有非均匀分布的随机整数数组

我想编写Java代码来生成范围[1,4]中的随机整数数组。 数组的长度为N,在运行时提供。 问题是范围[1,4]不是均匀分布的:

在此处输入图像描述

这意味着如果我创建N = 100的数组,数字’1’将在数组中平均出现40次,数字’2’出现10次,依此类推。

现在我使用此代码生成范围[1,4]中的均匀分布的随机数:

public static void main(String[] args) { int N; System.out.println(); System.out.print("Enter an integer number: "); N = input.nextInt(); int[] a = new int[N]; Random generator = new Random(); for(int i = 0; i < a.length; i++) { a[i] = generator.nextInt(4)+1; } } 

如何使用非均匀分布实现它,如上图所示?

从代码开始,这是一种方法:

 public static void main(String[] args){ int N; System.out.println(); System.out.print("Enter an integer number: "); N = input.nextInt(); int[] a = new int[N]; Random generator = new Random(); for (int i = 0; i < a.length; i++) { float n = generator.nextFloat(); if (n <= 0.4) { a[i] = 1; } else if (n <= 0.7) { a[i] = 3; } else if (n <= 0.9) { a[i] = 4; } else { a[i] = 2; } } } 

更新:在@pjs的建议下,按照破解概率的顺序选择数字,这样你就可以提前退出if块

另一个简单的解决方案是使用nextDouble(),它在[0,1)中生成一个随机双精度数。 如果值<.4则选择1,否则如果<(。4 + .2)选择2等,最后一个分支始终选择最后一个选项。 这很容易使用for循环进行推广。

对于更通用的方法,您可以使用分布概率填充NavigableMap

 double[] probs = {0.4, 0.1, 0.2, 0.3}; NavigableMap distribution = new TreeMap(); for(double p : probs) { distribution.put(distribution.isEmpty() ? p : distribution.lastKey() + p, distribution.size() + 1); } 

然后在[0,1>>范围内使用均匀分布的随机密钥查询地图:

 Random rnd = new Random(); for(int i=0; i<20; i++) { System.out.println(distribution.ceilingEntry(rnd.nextDouble()).getValue()); } 

这将使用以下键/值对填充地图:

 0.4 -> 1 0.5 -> 2 0.7 -> 3 1.0 -> 4 

要查询地图,首先要生成0到1范围内的均匀分布的double。使用ceilingEntry方法查询地图并传递随机数将返回“与最小键关联的映射大于或等于给定键” ,例如,传递范围<0.4,0.5的值将返回映射0.5 -> 2的条目。 因此,在返回的映射条目上使用getValue()将返回2。

a1, a2, a3a4是指定相对概率的两倍, s = a1+a2+a3+a4这意味着1的概率是a1/s2的概率是a2/s ,……

然后使用generator.nextDouble()创建一个随机的双d。

如果0 <= d < a1/s则整数应为1,

如果a1/s <= d < (a1+a2)/s则整数应为2

if (a1+a2)/s <= d < (a1+a2+a3)/s则整数应为3

if (a1+a2+a3)/s <= d < 1则整数应为4

对于您上面提到的具体问题,其他人提供的解决方案非常有效,而别名方法则过度。 但是,您在评论中说,您实际上将在具有更大范围的分布中使用它。 在这种情况下,设置别名表的开销可能值得获得实际生成值的O(1)行为。

这是Java的源代码。 如果你不想抓住Mersenne Twister,很容易将它还原为使用Java的股票Random

 /* * Created on Mar 12, 2007 * Feb 13, 2011: Updated to use Mersenne Twister - pjs */ package edu.nps.or.simutils; import java.lang.IllegalArgumentException; import java.text.DecimalFormat; import java.util.Comparator; import java.util.Stack; import java.util.PriorityQueue; import java.util.Random; import net.goui.util.MTRandom; public class AliasTable { private static Random r = new MTRandom(); private static DecimalFormat df2 = new DecimalFormat(" 0.00;-0.00"); private V[] primary; private V[] alias; private double[] primaryP; private double[] primaryPgivenCol; private static boolean notCloseEnough(double target, double value) { return Math.abs(target - value) > 1E-10; } /** * Constructs the AliasTable given the set of values * and corresponding probabilities. * @param value * An array of the set of outcome values for the distribution. * @param pOfValue * An array of corresponding probabilities for each outcome. * @throws IllegalArgumentException * The values and probability arrays must be of the same length, * the probabilities must all be positive, and they must sum to one. */ public AliasTable(V[] value, double[] pOfValue) { super(); if (value.length != pOfValue.length) { throw new IllegalArgumentException( "Args to AliasTable must be vectors of the same length."); } double total = 0.0; for (double d : pOfValue) { if (d < 0) { throw new IllegalArgumentException("p_values must all be positive."); } total += d; } if (notCloseEnough(1.0, total)) { throw new IllegalArgumentException("p_values must sum to 1.0"); } // Done with the safety checks, now let's do the work... // Cloning the values prevents people from changing outcomes // after the fact. primary = value.clone(); alias = value.clone(); primaryP = pOfValue.clone(); primaryPgivenCol = new double[primary.length]; for (int i = 0; i < primaryPgivenCol.length; ++i) { primaryPgivenCol[i] = 1.0; } double equiProb = 1.0 / primary.length; /* * Internal classes are UGLY!!!! * We're what you call experts. Don't try this at home! */ class pComparator implements Comparator { public int compare(Integer i1, Integer i2) { return primaryP[i1] < primaryP[i2] ? -1 : 1; } } PriorityQueue deficitSet = new PriorityQueue(primary.length, new pComparator()); Stack surplusSet = new Stack(); // initial allocation of values to deficit/surplus sets for (int i = 0; i < primary.length; ++i) { if (notCloseEnough(equiProb, primaryP[i])) { if (primaryP[i] < equiProb) { deficitSet.add(i); } else { surplusSet.add(i); } } } /* * Pull the largest deficit element from what remains. Grab as * much probability as you need from a surplus element. Re-allocate * the surplus element based on the amount of probability taken from * it to the deficit, surplus, or completed set. * * Lather, rinse, repeat. */ while (!deficitSet.isEmpty()) { int deficitColumn = deficitSet.poll(); int surplusColumn = surplusSet.pop(); primaryPgivenCol[deficitColumn] = primaryP[deficitColumn] / equiProb; alias[deficitColumn] = primary[surplusColumn]; primaryP[surplusColumn] -= equiProb - primaryP[deficitColumn]; if (notCloseEnough(equiProb, primaryP[surplusColumn])) { if (primaryP[surplusColumn] < equiProb) { deficitSet.add(surplusColumn); } else { surplusSet.add(surplusColumn); } } } } /** * Generate a value from the input distribution. The alias table * does this in O(1) time, regardless of the number of elements in * the distribution. * @return * A value from the specified distribution. */ public V generate() { int column = (int) (primary.length * r.nextDouble()); return r.nextDouble() <= primaryPgivenCol[column] ? primary[column] : alias[column]; } public void printAliasTable() { System.err.println("Primary\t\tprimaryPgivenCol\tAlias"); for(int i = 0; i < primary.length; ++i) { System.err.println(primary[i] + "\t\t\t" + df2.format(primaryPgivenCol[i]) + "\t\t" + alias[i]); } System.err.println(); } } 

一个稍微可扩展的Miquel版本(以及Teresa建议的版本):

  double[] distro=new double[]{.4,.1,.3,.2}; int N; System.out.println(); System.out.print("Enter an integer number: "); Scanner input = new Scanner(System.in); N = input.nextInt(); int[] a = new int[N]; Random generator = new Random(); outer: for(int i = 0; i < a.length; i++) { double rand=generator.nextDouble(); double val=0; for(int j=1;j