有没有一种有效的方法可以在给定总和或平均值的范围内生成N个随机整数?


11

是否有一种有效的方法来生成N个整数的随机组合,从而使-

  • 每个整数都在区间[ minmax]中,
  • 整数之和为sum
  • 整数可以按任何顺序出现(例如,随机顺序),并且
  • 从满足其他要求的所有组合中随机选择均匀组合吗?

对于随机组合,是否有一种类似的算法,其中整数必须按其值的排序顺序出现(而不是以任何顺序出现)?

(如果是,mean则选择平均值为的适当组合是一个特殊情况sum = N * mean。此问题等效于生成由sumN个部分组成的均匀随机分区,每个部分在[[ ]] 区间中minmax并按其顺序以任何顺序或排序顺序出现值(视情况而定)。)

我知道,对于以随机顺序出现的组合,可以通过以下方式解决此问题(EDIT [Apr. 27]:算法已修改。):

  1. 如果N * max < sumN * min > sum,则没有解决方案。

  2. 如果N * max == sum,则只有一个解,其中所有N数字都等于max。如果N * min == sum,则只有一个解,其中所有N数字都等于min

  3. 使用 Smith和Tromble中给出的算法(“从单位单纯形抽样”,2004年),生成N个具有和的随机非负整数sum - N * min

  4. 添加min到这种方式生成每个号码。

  5. 如果任何数字大于max,请转到步骤3。

但是,如果该算法max远小于,则该算法速度较慢sum。例如,根据我的测试(使用上述涉及的特殊情况的实现mean),该算法平均会拒绝-

  • 约1.6个样本N = 7, min = 3, max = 10, sum = 42,但
  • 如果大约30.6个样本N = 20, min = 3, max = 10, sum = 120

有没有办法修改此算法以使其对大N高效,同时仍满足上述要求?

编辑:

作为注释中的替代建议,一种有效的产生有效随机组合(满足除最后一个条件之外的条件)的有效方法是:

  1. 计算X,尽可能为有效组合的数量summinmax
  2. 选择Y中的一个统一随机整数[0, X)
  3. 将(“ unrank”)Y转换为有效组合。

但是,是否存在用于计算有效组合(或排列)数量的公式,并且有办法将整数转换为有效组合?[编辑(4月28日):对于排列而非组合,相同。

编辑(4月27日):

阅读了Devroye的非均匀随机变量生成(1986)后,我可以确认这是生成随机分区的问题。同样,第661页的练习2(尤其是E部分)与此问题相关。

编辑(4月28日):

事实证明,我给出的算法是统一的,其中所涉及的整数是以随机顺序给出的,而不是按其值排序的顺序。由于这两个问题都是人们普遍关心的问题,因此我对这个问题进行了修改,以寻求针对这两个问题的规范答案。

以下Ruby代码可用于验证一致性的潜在解决方案(algorithm(...)候选算法在哪里):

combos={}
permus={}
mn=0
mx=6
sum=12
for x in mn..mx
  for y in mn..mx
    for z in mn..mx
      if x+y+z==sum
        permus[[x,y,z]]=0
      end
      if x+y+z==sum and x<=y and y<=z
        combos[[x,y,z]]=0
      end
    end
  end
end

3000.times {|x|
 f=algorithm(3,sum,mn,mx)
 combos[f.sort]+=1
 permus[f]+=1
}
p combos
p permus

编辑(4月29日):重新添加了当前实现的Ruby代码。

以下代码示例是用Ruby提供的,但我的问题与编程语言无关:

def posintwithsum(n, total)
    raise if n <= 0 or total <=0
    ls = [0]
    ret = []
    while ls.length < n
      c = 1+rand(total-1)
      found = false
      for j in 1...ls.length
        if ls[j] == c
          found = true
          break
        end
      end
      if found == false;ls.push(c);end
    end
    ls.sort!
    ls.push(total)
    for i in 1...ls.length
       ret.push(ls[i] - ls[i - 1])
    end
    return ret
end

def integersWithSum(n, total)
 raise if n <= 0 or total <=0
 ret = posintwithsum(n, total + n)
 for i in 0...ret.length
    ret[i] = ret[i] - 1
 end
 return ret
end

# Generate 100 valid samples
mn=3
mx=10
sum=42
n=7
100.times {
 while true
    pp=integersWithSum(n,sum-n*mn).map{|x| x+mn }
    if !pp.find{|x| x>mx }
      p pp; break # Output the sample and break
    end
 end
}

您能否阐明您的第三个要求?您是否需要所有可能组合(包括均值错误的组合)或所有有效组合(即均值正确的组合)之间的一致性?
user58697

所有有效组合,即满足其他要求的所有组合。
Peter O.

如果我们有一种方法可以对以[min,max]限制为N个整数的总和的分区进行计数和取消排序,那么随机选择这些分区之一并进行不排序可以代表均匀的分布,这样会比您当前的方法更有效吗?和N可以有多大?
גלעדברקן

我不知道您对“不进行总和划分”的意思,并且我不知道有证据表明这样做会导致这个问题含义内的均匀分布。对于这个问题,sumN都实际上是无限的(在合理范围内)。我正在寻找一个规范的答案,因为基本问题会在Stack Overflow上提出的许多问题中弹出,包括thisthis。@גלעדברקן
彼得O.

如果我们给每个可能的组合一个有序排列的“等级”(或索引),则“取消排名”将意味着根据给定的等级(当然还有N,min和max)生成组合。为什么从所有可能的组合中选择一种不符合均匀分布?
גלעדברקן

Answers:


3

这是我在Java中的解决方案。它功能齐全,包含两个生成器:PermutationPartitionGenerator用于未排序的分区和CombinationPartitionGenerator用于已排序的分区。您的生成器也在类中实现以SmithTromblePartitionGenerator进行比较。该类SequentialEnumerator按顺序枚举所有可能的分区(未排序或已排序,取决于参数)。我已经为所有这些生成器添加了全面的测试(包括您的测试用例)。大多数情况下,该实现是可以自解释的。如果您有任何问题,我会在几天后回答。

import java.util.Random;
import java.util.function.Supplier;

public abstract class PartitionGenerator implements Supplier<int[]>{
    public static final Random rand = new Random();
    protected final int numberCount;
    protected final int min;
    protected final int range;
    protected final int sum; // shifted sum
    protected final boolean sorted;

    protected PartitionGenerator(int numberCount, int min, int max, int sum, boolean sorted) {
        if (numberCount <= 0)
            throw new IllegalArgumentException("Number count should be positive");
        this.numberCount = numberCount;
        this.min = min;
        range = max - min;
        if (range < 0)
            throw new IllegalArgumentException("min > max");
        sum -= numberCount * min;
        if (sum < 0)
            throw new IllegalArgumentException("Sum is too small");
        if (numberCount * range < sum)
            throw new IllegalArgumentException("Sum is too large");
        this.sum = sum;
        this.sorted = sorted;
    }

    // Whether this generator returns sorted arrays (i.e. combinations)
    public final boolean isSorted() {
        return sorted;
    }

    public interface GeneratorFactory {
        PartitionGenerator create(int numberCount, int min, int max, int sum);
    }
}

import java.math.BigInteger;

// Permutations with repetition (i.e. unsorted vectors) with given sum
public class PermutationPartitionGenerator extends PartitionGenerator {
    private final double[][] distributionTable;

    public PermutationPartitionGenerator(int numberCount, int min, int max, int sum) {
        super(numberCount, min, max, sum, false);
        distributionTable = calculateSolutionCountTable();
    }

    private double[][] calculateSolutionCountTable() {
        double[][] table = new double[numberCount + 1][sum + 1];
        BigInteger[] a = new BigInteger[sum + 1];
        BigInteger[] b = new BigInteger[sum + 1];
        for (int i = 1; i <= sum; i++)
            a[i] = BigInteger.ZERO;
        a[0] = BigInteger.ONE;
        table[0][0] = 1.0;
        for (int n = 1; n <= numberCount; n++) {
            double[] t = table[n];
            for (int s = 0; s <= sum; s++) {
                BigInteger z = BigInteger.ZERO;
                for (int i = Math.max(0, s - range); i <= s; i++)
                    z = z.add(a[i]);
                b[s] = z;
                t[s] = z.doubleValue();
            }
            // swap a and b
            BigInteger[] c = b;
            b = a;
            a = c;
        }
        return table;
    }

    @Override
    public int[] get() {
        int[] p = new int[numberCount];
        int s = sum; // current sum
        for (int i = numberCount - 1; i >= 0; i--) {
            double t = rand.nextDouble() * distributionTable[i + 1][s];
            double[] tableRow = distributionTable[i];
            int oldSum = s;
            // lowerBound is introduced only for safety, it shouldn't be crossed 
            int lowerBound = s - range;
            if (lowerBound < 0)
                lowerBound = 0;
            s++;
            do
                t -= tableRow[--s];
            // s can be equal to lowerBound here with t > 0 only due to imprecise subtraction
            while (t > 0 && s > lowerBound);
            p[i] = min + (oldSum - s);
        }
        assert s == 0;
        return p;
    }

    public static final GeneratorFactory factory = (numberCount, min, max,sum) ->
        new PermutationPartitionGenerator(numberCount, min, max, sum);
}

import java.math.BigInteger;

// Combinations with repetition (i.e. sorted vectors) with given sum 
public class CombinationPartitionGenerator extends PartitionGenerator {
    private final double[][][] distributionTable;

    public CombinationPartitionGenerator(int numberCount, int min, int max, int sum) {
        super(numberCount, min, max, sum, true);
        distributionTable = calculateSolutionCountTable();
    }

    private double[][][] calculateSolutionCountTable() {
        double[][][] table = new double[numberCount + 1][range + 1][sum + 1];
        BigInteger[][] a = new BigInteger[range + 1][sum + 1];
        BigInteger[][] b = new BigInteger[range + 1][sum + 1];
        double[][] t = table[0];
        for (int m = 0; m <= range; m++) {
            a[m][0] = BigInteger.ONE;
            t[m][0] = 1.0;
            for (int s = 1; s <= sum; s++) {
                a[m][s] = BigInteger.ZERO;
                t[m][s] = 0.0;
            }
        }
        for (int n = 1; n <= numberCount; n++) {
            t = table[n];
            for (int m = 0; m <= range; m++)
                for (int s = 0; s <= sum; s++) {
                    BigInteger z;
                    if (m == 0)
                        z = a[0][s];
                    else {
                        z = b[m - 1][s];
                        if (m <= s)
                            z = z.add(a[m][s - m]);
                    }
                    b[m][s] = z;
                    t[m][s] = z.doubleValue();
                }
            // swap a and b
            BigInteger[][] c = b;
            b = a;
            a = c;
        }
        return table;
    }

    @Override
    public int[] get() {
        int[] p = new int[numberCount];
        int m = range; // current max
        int s = sum; // current sum
        for (int i = numberCount - 1; i >= 0; i--) {
            double t = rand.nextDouble() * distributionTable[i + 1][m][s];
            double[][] tableCut = distributionTable[i];
            if (s < m)
                m = s;
            s -= m;
            while (true) {
                t -= tableCut[m][s];
                // m can be 0 here with t > 0 only due to imprecise subtraction
                if (t <= 0 || m == 0)
                    break;
                m--;
                s++;
            }
            p[i] = min + m;
        }
        assert s == 0;
        return p;
    }

    public static final GeneratorFactory factory = (numberCount, min, max, sum) ->
        new CombinationPartitionGenerator(numberCount, min, max, sum);
}

import java.util.*;

public class SmithTromblePartitionGenerator extends PartitionGenerator {
    public SmithTromblePartitionGenerator(int numberCount, int min, int max, int sum) {
        super(numberCount, min, max, sum, false);
    }

    @Override
    public int[] get() {
        List<Integer> ls = new ArrayList<>(numberCount + 1);
        int[] ret = new int[numberCount];
        int increasedSum = sum + numberCount;
        while (true) {
            ls.add(0);
            while (ls.size() < numberCount) {
                int c = 1 + rand.nextInt(increasedSum - 1);
                if (!ls.contains(c))
                    ls.add(c);
            }
            Collections.sort(ls);
            ls.add(increasedSum);
            boolean good = true;
            for (int i = 0; i < numberCount; i++) {
                int x = ls.get(i + 1) - ls.get(i) - 1;
                if (x > range) {
                    good = false;
                    break;
                }
                ret[i] = x;
            }
            if (good) {
                for (int i = 0; i < numberCount; i++)
                    ret[i] += min;
                return ret;
            }
            ls.clear();
        }
    }

    public static final GeneratorFactory factory = (numberCount, min, max, sum) ->
        new SmithTromblePartitionGenerator(numberCount, min, max, sum);
}

import java.util.Arrays;

// Enumerates all partitions with given parameters
public class SequentialEnumerator extends PartitionGenerator {
    private final int max;
    private final int[] p;
    private boolean finished;

    public SequentialEnumerator(int numberCount, int min, int max, int sum, boolean sorted) {
        super(numberCount, min, max, sum, sorted);
        this.max = max;
        p = new int[numberCount];
        startOver();
    }

    private void startOver() {
        finished = false;
        int unshiftedSum = sum + numberCount * min;
        fillMinimal(0, Math.max(min, unshiftedSum - (numberCount - 1) * max), unshiftedSum);
    }

    private void fillMinimal(int beginIndex, int minValue, int fillSum) {
        int fillRange = max - minValue;
        if (fillRange == 0)
            Arrays.fill(p, beginIndex, numberCount, max);
        else {
            int fillCount = numberCount - beginIndex;
            fillSum -= fillCount * minValue;
            int maxCount = fillSum / fillRange;
            int maxStartIndex = numberCount - maxCount;
            Arrays.fill(p, maxStartIndex, numberCount, max);
            fillSum -= maxCount * fillRange;
            Arrays.fill(p, beginIndex, maxStartIndex, minValue);
            if (fillSum != 0)
                p[maxStartIndex - 1] = minValue + fillSum;
        }
    }

    @Override
    public int[] get() { // returns null when there is no more partition, then starts over
        if (finished) {
            startOver();
            return null;
        }
        int[] pCopy = p.clone();
        if (numberCount > 1) {
            int i = numberCount;
            int s = p[--i];
            while (i > 0) {
                int x = p[--i];
                if (x == max) {
                    s += x;
                    continue;
                }
                x++;
                s--;
                int minRest = sorted ? x : min;
                if (s < minRest * (numberCount - i - 1)) {
                    s += x;
                    continue;
                }
                p[i++]++;
                fillMinimal(i, minRest, s);
                return pCopy;
            }
        }
        finished = true;
        return pCopy;
    }

    public static final GeneratorFactory permutationFactory = (numberCount, min, max, sum) ->
        new SequentialEnumerator(numberCount, min, max, sum, false);
    public static final GeneratorFactory combinationFactory = (numberCount, min, max, sum) ->
        new SequentialEnumerator(numberCount, min, max, sum, true);
}

import java.util.*;
import java.util.function.BiConsumer;
import PartitionGenerator.GeneratorFactory;

public class Test {
    private final int numberCount;
    private final int min;
    private final int max;
    private final int sum;
    private final int repeatCount;
    private final BiConsumer<PartitionGenerator, Test> procedure;

    public Test(int numberCount, int min, int max, int sum, int repeatCount,
            BiConsumer<PartitionGenerator, Test> procedure) {
        this.numberCount = numberCount;
        this.min = min;
        this.max = max;
        this.sum = sum;
        this.repeatCount = repeatCount;
        this.procedure = procedure;
    }

    @Override
    public String toString() {
        return String.format("=== %d numbers from [%d, %d] with sum %d, %d iterations ===",
                numberCount, min, max, sum, repeatCount);
    }

    private static class GeneratedVector {
        final int[] v;

        GeneratedVector(int[] vect) {
            v = vect;
        }

        @Override
        public int hashCode() {
            return Arrays.hashCode(v);
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            return Arrays.equals(v, ((GeneratedVector)obj).v);
        }

        @Override
        public String toString() {
            return Arrays.toString(v);
        }
    }

    private static final Comparator<Map.Entry<GeneratedVector, Integer>> lexicographical = (e1, e2) -> {
        int[] v1 = e1.getKey().v;
        int[] v2 = e2.getKey().v;
        int len = v1.length;
        int d = len - v2.length;
        if (d != 0)
            return d;
        for (int i = 0; i < len; i++) {
            d = v1[i] - v2[i];
            if (d != 0)
                return d;
        }
        return 0;
    };

    private static final Comparator<Map.Entry<GeneratedVector, Integer>> byCount =
            Comparator.<Map.Entry<GeneratedVector, Integer>>comparingInt(Map.Entry::getValue)
            .thenComparing(lexicographical);

    public static int SHOW_MISSING_LIMIT = 10;

    private static void checkMissingPartitions(Map<GeneratedVector, Integer> map, PartitionGenerator reference) {
        int missingCount = 0;
        while (true) {
            int[] v = reference.get();
            if (v == null)
                break;
            GeneratedVector gv = new GeneratedVector(v);
            if (!map.containsKey(gv)) {
                if (missingCount == 0)
                    System.out.println(" Missing:");
                if (++missingCount > SHOW_MISSING_LIMIT) {
                    System.out.println("  . . .");
                    break;
                }
                System.out.println(gv);
            }
        }
    }

    public static final BiConsumer<PartitionGenerator, Test> distributionTest(boolean sortByCount) {
        return (PartitionGenerator gen, Test test) -> {
            System.out.print("\n" + getName(gen) + "\n\n");
            Map<GeneratedVector, Integer> combos = new HashMap<>();
            // There's no point of checking permus for sorted generators
            // because they are the same as combos for them
            Map<GeneratedVector, Integer> permus = gen.isSorted() ? null : new HashMap<>();
            for (int i = 0; i < test.repeatCount; i++) {
                int[] v = gen.get();
                if (v == null && gen instanceof SequentialEnumerator)
                    break;
                if (permus != null) {
                    permus.merge(new GeneratedVector(v), 1, Integer::sum);
                    v = v.clone();
                    Arrays.sort(v);
                }
                combos.merge(new GeneratedVector(v), 1, Integer::sum);
            }
            Set<Map.Entry<GeneratedVector, Integer>> sortedEntries = new TreeSet<>(
                    sortByCount ? byCount : lexicographical);
            System.out.println("Combos" + (gen.isSorted() ? ":" : " (don't have to be uniform):"));
            sortedEntries.addAll(combos.entrySet());
            for (Map.Entry<GeneratedVector, Integer> e : sortedEntries)
                System.out.println(e);
            checkMissingPartitions(combos, test.getGenerator(SequentialEnumerator.combinationFactory));
            if (permus != null) {
                System.out.println("\nPermus:");
                sortedEntries.clear();
                sortedEntries.addAll(permus.entrySet());
                for (Map.Entry<GeneratedVector, Integer> e : sortedEntries)
                    System.out.println(e);
                checkMissingPartitions(permus, test.getGenerator(SequentialEnumerator.permutationFactory));
            }
        };
    }

    public static final BiConsumer<PartitionGenerator, Test> correctnessTest =
        (PartitionGenerator gen, Test test) -> {
        String genName = getName(gen);
        for (int i = 0; i < test.repeatCount; i++) {
            int[] v = gen.get();
            if (v == null && gen instanceof SequentialEnumerator)
                v = gen.get();
            if (v.length != test.numberCount)
                throw new RuntimeException(genName + ": array of wrong length");
            int s = 0;
            if (gen.isSorted()) {
                if (v[0] < test.min || v[v.length - 1] > test.max)
                    throw new RuntimeException(genName + ": generated number is out of range");
                int prev = test.min;
                for (int x : v) {
                    if (x < prev)
                        throw new RuntimeException(genName + ": unsorted array");
                    s += x;
                    prev = x;
                }
            } else
                for (int x : v) {
                    if (x < test.min || x > test.max)
                        throw new RuntimeException(genName + ": generated number is out of range");
                    s += x;
                }
            if (s != test.sum)
                throw new RuntimeException(genName + ": wrong sum");
        }
        System.out.format("%30s :   correctness test passed%n", genName);
    };

    public static final BiConsumer<PartitionGenerator, Test> performanceTest =
        (PartitionGenerator gen, Test test) -> {
        long time = System.nanoTime();
        for (int i = 0; i < test.repeatCount; i++)
            gen.get();
        time = System.nanoTime() - time;
        System.out.format("%30s : %8.3f s %10.0f ns/test%n", getName(gen), time * 1e-9, time * 1.0 / test.repeatCount);
    };

    public PartitionGenerator getGenerator(GeneratorFactory factory) {
        return factory.create(numberCount, min, max, sum);
    }

    public static String getName(PartitionGenerator gen) {
        String name = gen.getClass().getSimpleName();
        if (gen instanceof SequentialEnumerator)
            return (gen.isSorted() ? "Sorted " : "Unsorted ") + name;
        else
            return name;
    }

    public static GeneratorFactory[] factories = { SmithTromblePartitionGenerator.factory,
            PermutationPartitionGenerator.factory, CombinationPartitionGenerator.factory,
            SequentialEnumerator.permutationFactory, SequentialEnumerator.combinationFactory };

    public static void main(String[] args) {
        Test[] tests = {
                            new Test(3, 0, 3, 5, 3_000, distributionTest(false)),
                            new Test(3, 0, 6, 12, 3_000, distributionTest(true)),
                            new Test(50, -10, 20, 70, 2_000, correctnessTest),
                            new Test(7, 3, 10, 42, 1_000_000, performanceTest),
                            new Test(20, 3, 10, 120, 100_000, performanceTest)
                       };
        for (Test t : tests) {
            System.out.println(t);
            for (GeneratorFactory factory : factories) {
                PartitionGenerator candidate = t.getGenerator(factory);
                t.procedure.accept(candidate, t);
            }
            System.out.println();
        }
    }
}

您可以在Ideone上尝试


感谢您的回答; 它运作良好。我在这里的另一个答案中描述了排列生成器;在您的帮助下回答了另一个问题;并将很快将您的算法包含在我的有关随机生成方法的文章的Python示例代码中。
Peter O.

只是要清楚。该算法是否依赖于生成所有可能的分区/组合以进行采样?
约瑟夫·伍德

@JosephWood不,它依赖于对所有这些进行计数。在生成器初始化时,此操作仅执行一次,因此非常有效,因为它利用了动态编程方法。
约翰·麦克莱恩

动态规划如何解决选择“和”的均匀随机分割成N个整数随机选择的相关问题与更换从列表(例如或)无需更换例如),或如何这个问题,否则解决呢?
Peter O.

@PeterO。您需要通过与我的算法相同的方法对所有可能的分区进行计数,但是这次您只需要从总和中减去允许的数字。评论时间太长,您可以提出一个单独的问题。我怀疑可以通过同一方法解决四个不同的问题。假设您有一个不同的整数列表供您选择(这只是该问题的一个连续范围)。然后,如果应该对数组进行排序/不排序并允许/禁止重复,则可以从此列表生成具有给定长度的,由给定长度组成的给定长度的随机数组。
约翰·麦克莱恩

1

这是John McClane的PermutationPartitionGenerator中的算法,在此页面的另一个答案中。它具有两个阶段,即设置阶段和采样阶段,并n在[ minmax]中生成具有sum的随机数sum,其中数字以随机顺序列出。

设置阶段:首先,使用以下公式构建解决方案表(t(y, x)其中y[0,n] x中,[0,sum - n * min]中):

  • 如果j == 0,则t(0,j)= 1; 否则为0
  • t(i,j)= t(i-1,j)+ t(i-1,j-1)+ ... + t(i-1,j-(max-min))

在这里,t(y,x)存储y(在适当范围内)数字总和相等的相对概率x。该概率相对于所有具有相同的t(y,x)y

采样阶段:在这里我们生成n数字样本。设置ssum - n * min,然后对于每个位置i,从n - 10 开始并向后工作:

  • 设置v为[0,t(i + 1,s))中的随机整数。
  • 设置rmin
  • 从中减去t(i,s)v
  • v保持为0或更大时,从中减去t(i,s-1)v,将加上1 r,然后从减去1 s
  • i样本中位置的数字设置为r

编辑:

似乎通过对上述算法进行了细微的更改,就有可能使每个随机数使用一个单独的范围,而不是对所有这些随机数使用相同的范围:

位置i∈[0,n)处的每个随机数具有最小值min(i)和最大值max(i)。

adjsum= sum-Σmin(i)。

设置阶段:首先,使用以下公式构建解决方案表(t(y, x)其中y[0,n] x中,[0,adjsum]中):

  • 如果j == 0,则t(0,j)= 1; 否则为0
  • t(i,j)= t(i-1,j)+ t(i-1,j-1)+ ... + t(i-1,j- (max(i-1)-min(i -1))

采样阶段与之前完全相同,除了我们将设置sadjsum(而不是sum - n * min)并将其设置r为min(i)(而不是min)。


编辑:

对于John McClane的CombinationPartitionGenerator,设置和采样阶段如下。

设置阶段:首先,使用以下公式构建解决方案表(t(z, y, x)其中z[0,n],y[0,max - min]和x[0,sum - n * min]):

  • 如果k == 0,则t(0,j,k)= 1; 否则为0
  • t(i,0,k)= t(i-1,0,k)
  • t(i,j,k)= t(i,j-1,k)+ t(i-1,j,k-j)

采样阶段:在这里我们生成n数字样本。设置ssum - n * minmrangemax - min,然后为每个位置i,开始n - 1和向后工作,以0:

  • 设置v为[0,t(i + 1,mrange,s))中的随机整数。
  • 设置mrange为min(mranges
  • 减去mranges
  • 设置rmin + mrange
  • 减法吨(imranges)从v
  • v保持为0或更大,加1 s,减1从r来自和1 mrange,然后减去吨(imranges从)v
  • i样本中位置的数字设置为r

0

我尚未对此进行测试,因此它并不是真正的答案,只是尝试尝试的时间太长,无法放入评论中。从一个满足前两个条件的数组开始,并继续使用它,以便它仍然满足前两个条件,但是随机性更高。

如果均值是整数,则您的初始数组可以为[4,4,4,... 4]或[3,4,5,3,4,5,... 5,8,0]或像这样简单的事情。平均为4.5,请尝试[4、5、4、5,... 4、5]。

接下来,在数组中选择一对数字num1num2。可能应该按照顺序选择第一个数字,就像使用Fisher-Yates混洗一样,应该随机选择第二个数字。按顺序排列第一个数字可确保每个数字至少被选择一次。

现在计算max-num1num2-min。这些是两个数字到maxmin边界的距离。设置limit为两个距离中较小的一个。这是允许的最大更改,不会将一个或另一个数字超出允许的限制。如果limit为零,则跳过该对。

选择[1,limit] 范围内的随机整数:调用它change。我在可选取范围内省略了0,因为它没有作用。测试可能表明,通过包含随机性,您可以获得更好的随机性;我不确定。

现在设置num1 <- num1 + changenum2 <- num2 - change。这不会影响平均值,并且数组的所有元素仍在所需的边界内。

您将需要至少遍历整个数组一次。测试应该表明您是否需要多次进行测试以获得足够随机的内容。

预计到达时间:包含伪代码

// Set up the array.
resultAry <- new array size N
for (i <- 0 to N-1)
  // More complex initial setup schemes are possible here.
  resultAry[i] <- mean
rof

// Munge the array entries.
for (ix1 <- 0 to N-1)  // ix1 steps through the array in order.

  // Pick second entry different from first.
  repeat
    ix2 <- random(0, N-1)
  until (ix2 != ix1)

  // Calculate size of allowed change.
  hiLimit <- max - resultAry[ix1]
  loLimit <- resultAry[ix2] - min
  limit <- minimum(hiLimit, loLimit)
  if (limit == 0)
    // No change possible so skip.
    continue loop with next ix1
  fi

  // Change the two entries keeping same mean.
  change <- random(1, limit)  // Or (0, limit) possibly.
  resultAry[ix1] <- resultAry[ix1] + change
  resultAry[ix2] <- resultAry[ix2] - change

rof

// Check array has been sufficiently munged.
if (resultAry not random enough)
  munge the array again
fi

我已经对其进行了测试,但不幸的是,无论我进行了多少次迭代,您的算法都无法形成所有解决方案的统一分布。
Peter O.

那好吧。无论如何都值得尝试。:(
rossum
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.