【问题标题】:Generating random integers with a difference constraint生成具有差异约束的随机整数
【发布时间】:2014-03-25 16:22:20
【问题描述】:

我有以下问题:

从 0-N 范围内生成 M 个均匀随机整数,其中 N >> M,并且没有一对的差值小于 K。其中 M >> K

目前我能想到的最好的方法是维护一个排序列表,然后确定当前生成的整数的下限,并用上下元素测试它,如果可以,则在两者之间插入元素.这是 O(nlogn) 的复杂度。

会不会有更高效的算法?

问题的一个例子:

生成1000个0到1亿之间的均匀随机整数,其中任意两个整数之差不小于1000

解决此问题的综合方法是:

  1. 确定满足约束条件的所有 n-choose-m 组合,我们称之为集合 X
  2. 在 [0,|X|) 范围内选择一个均匀随机整数 i。
  3. 从 X 中选择第 i 个组合作为结果。

当 n-choose-m 很大时,这个解决方案是有问题的,因为枚举和存储所有可能的组合将非常昂贵。因此寻求一种高效的在线生成解决方案。

注意:以下是pentadecagon

提供的解决方案的C++实现
std::vector<int> generate_random(const int n, const int m, const int k)
{
   if ((n < m) || (m < k))
      return std::vector<int>();

   std::random_device source;
   std::mt19937 generator(source());
   std::uniform_int_distribution<> distribution(0, n - (m - 1) * k);

   std::vector<int> result_list;
   result_list.reserve(m);

   for (int i = 0; i < m; ++i)
   {
      result_list.push_back(distribution(generator));
   }

   std::sort(std::begin(result_list),std::end(result_list));

   for (int i = 0; i < m; ++i)
   {
      result_list[i] += (i * k);
   }

   return result_list;
}

http://ideone.com/KOeR4R

.

【问题讨论】:

  • 分布应该如何?有固定数量的可能结果。所有这些都应该具有相同的概率吗?
  • @Heuster:'分布应该如何?'均匀分布。
  • 我认为您的示例无效,因为 1000 >> 1000 不正确。
  • (@DavidEisenstat 如果我错了,请纠正我,我认为这应该可行:) 对包含 0N - (M - 1)*K + K 之间数字的数组进行 Fisher-Yates 洗牌,取最后 K 结果数组的数字。这为您提供了上述间隔的大小K 的均匀随机子集。您可以使用它来构建整数N - (M - 1)*KK + 1 组合,方法是将K 子集用作N - (M - 1)*K 的一元表示中的逗号(参见here 以了解说明)。
  • @G.Bach 它可能会起作用,但是边界条件可能又会出现问题。你能仔细写下来并作为答案发布吗? (顺便说一下,有更多节省空间的方法来生成随机子集。)

标签: c++ algorithm random constraints unique


【解决方案1】:

编辑:我根据创建有序序列的要求调整了文本,每个序列具有相同的概率。

i=0..M-1 创建不重复的随机数a_i。对它们进行排序。然后创建数字

b_i=a_i + i*(K-1)

鉴于构造,这些数字b_i 具有所需的间隙,因为a_i 已经具有至少1 的间隙。为了确保这些 b 值完全覆盖所需的范围 [1..N],您必须确保从范围 [1..N-(M-1)*(K-1)] 中选择 a_i。这样你就可以得到真正独立的数字。好吧,考虑到所需的差距,尽可能独立。由于排序,您再次获得 O(M log M) 性能,但这应该不会太糟糕。排序通常非常快。在 Python 中它看起来像这样:

import random
def random_list( N, M, K ):
    s = set()
    while len(s) < M:
        s.add( random.randint( 1, N-(M-1)*(K-1) ) )

    res = sorted( s )

    for i in range(M):
        res[i] += i * (K-1)

    return res

【讨论】:

  • 很抱歉在上面诽谤这个答案。现在我仔细阅读它看起来是正确的。
  • 想一想,我不太确定这会产生均匀分布。这种方法将每个排序序列 (a_0,...,a_(M-1)) 映射到一个解决方案集。为了得到解集 (0,K,2K,...,(M-1)K),您需要绘制序列 (0,...,0),其概率为 (N-(M- 1)*K)^(-M)。现在以序列 (1,1,2,3,...,M-1) 的结果为例。获得该序列的概率至少是 (0,...,0) 的两倍,因为您可以绘制 (1,2,...,M-1,1) 和 (1,1,2) ,...,M-1) 在排序之前,例如。这不应该给出更像正态分布的东西吗?
  • 这就是排序随机数的问题。当您掷两个骰子并对结果进行排序时,得到 1,2 的可能性是 1,1 的两倍。它仍然是均匀分布。如果您想要未排序的数字,您可以记住原始顺序并在添加 i*K 后恢复它。或者你只是创建一个随机排列。
  • 对于您给出的在一组有效解决方案上产生均匀分布的算法(我认为这是 Soda Coader 正在寻找的),您必须均匀地绘制排序序列;否则对那些从具有更高概率的排序序列生成的解决方案存在偏差,不是吗?您是说您的算法产生均匀分布;但在什么范围内?
  • @G.Bach 不。生日悖论是关于发现任何碰撞,而不是关于它们的频率。只要少于 50% 的数字被占用,碰撞的概率就会保持在 50% 以下。这就是 N>M*(K+1) 的情况。
【解决方案2】:

首先:这将试图表明(M+1)-compositions 之间存在双射(稍作修改,我们将允许加数为0)值N - (M-1)*K 和您的问题的有效解决方案。之后,我们只需要均匀地随机选择其中一个构图并应用双射即可。


双射:

然后 xi 形成左侧值的M+1-composition(允许0 加数)(注意 xi 不必须是单调递增的!)。

由此我们得到一个有效的解决方案

通过如下设置值 mi

我们看到mi和mi + 1之间的距离至少是K,而mM最多是N(比较我们开始时对构图的选择)。这意味着满足上述条件的每个(M+1)-composition 都为您的问题定义了一个有效的解决方案。 (您会注意到,我们只使用 xM 作为使总和正确的方法,我们不使用它来构造 mi .)

要看到这给出了双射,我们需要看到构造可以反转;为此,让

成为满足您条件的给定解决方案。要获得构成 this 的组合,请按如下方式定义 xi

首先,所有 xi 至少是 0,所以没关系。要查看它们是否构成上述值的有效组合(同样,每个 xi 都允许为 0),请考虑:

第三个等式紧随其后,因为我们有这个可伸缩的和几乎抵消了所有 mi

所以我们已经看到,所描述的构造在所描述的N - (M-1)*K 组合与您的问题的有效解决方案之间产生了双射。我们现在要做的就是随机均匀地选择其中一种组合物,然后应用构造来获得解决方案。


随机均匀地选择一个构图

每个描述的组合都可以通过以下方式唯一标识(比较this 以进行说明):为该值的一元表示法保留N - (M-1)*K 空格,为M 逗号保留另一个M 空格。我们通过在N - (M-1)*K + M 空格中选择M 得到(M+1)- 组合N - (M-1)*K,将逗号放在那里,然后用| 填充其余部分。然后让 x0 是第一个逗号之前的| 的数量,xM+1 是最后一个逗号之后的| 的数量,所有其他的 xi 逗号ii+1 之间的| 的数量。所以我们所要做的就是随机均匀地选择整数区间[1; N - (M-1)*K + M]M-element 子集,例如我们可以使用O(N + M log M) 中的Fisher-Yates shuffle 来做到这一点(我们需要对@987654357 进行排序@分隔符来构建组合)因为M*K需要在O(N)中才能存在任何解决方案。所以如果NM 大至少一个对数因子,那么这在N 中是线性的。


注意:@DavidEisenstat 建议有更多节省空间的方法来选择该区间的 M-element 子集;恐怕我不知道。


您可以通过执行我们从上面的构造中得到的简单输入验证得到一个防错算法,即N ≥ (M-1) * K 并且所有三个值至少为1(或0,如果您定义空集作为该案例的有效解决方案)。

【讨论】:

  • Sampling a random subset。我相信这个答案正确地提取了一个统一的样本。
  • 这相当冗长,但也是一个非常有趣和全面的解释。谢谢。
  • @G。 Bach 对于给定的 N,M,K,考虑到所有可行的组合,如果要确定每个组合中连续元素之间的 (M-1) 个差异,那么连续差异的分布是否是均匀分布的?
  • @SodaCoader 我不确定我是否理解这个问题。我们绘制的是由 M 个元素组成的解决方案集,其中按排序顺序,每个元素与其前任和后继的距离至少为 K。对于存在解集的任何 (N,M,K),可能的差异序列在有效解集中的出现次数是已知的,并且它们因增量序列而异。例如,差分序列 (K,K,...,K) 恰好出现在 N-(M-1)*K 个有效解中,而差分序列 (N-(M-1)*K,K,K ,...,K) 仅适用于一个有效的解决方案集。
【解决方案3】:

为什么不这样做:

for (int i = 0; i < M; ++i) {
  pick a random number between K and N/M
  add this number to (N/M)* i;

现在你有 M 个随机数,沿 N 均匀分布,所有这些随机数至少相差 K。它在 O(n) 时间内。作为额外的奖励,它已经排序。 :-)

编辑:

其实“选择一个随机数”部分不应该在K和N/M之间,而是在min(K, [K - (N/M * i - previous value)])之间。这将确保差异仍然至少为 K,并且不排除不应遗漏的值。

第二次编辑:

嗯,第一种情况不应该在 K 和 N/M 之间 - 它应该在 0 和 N/M 之间。就像您在接近 N/M*i 边界时需要特殊的外壳一样,我们需要特殊的初始外壳。

除此之外,您在 cmets 中提出的问题是公平代表,您是对的。当我的伪代码出现时,它目前完全忽略了 N/M*M 和 N 之间的多余部分。这是另一个极端情况;只需更改最后一个范围的随机值。

现在,在这种情况下,最后一个范围的分布将有所不同。由于您有更多数字,因此每个数字的机会比所有其他范围的机会要少一些。我的理解是,因为您使用的是“>>”,所以这不应该真正影响分布,即样本集中的大小差异应该是名义上的。但如果你想让它更公平,你可以在每个范围内平均分配超出部分。这使您的初始范围计算更加复杂 - 您必须根据余数除以 M 来增加每个范围。

有很多特殊情况需要注意,但都可以处理。我将伪代码保持得很基本,只是为了确保清楚地了解一般概念。如果不出意外,这应该是一个很好的起点。

第三次也是最后一次编辑:

对于那些担心分布具有强制均匀性的人,我仍然声称没有什么说它不能。选择均匀地分布在每个段中。有一种线性方法可以使其保持不均匀,但这也需要权衡:如果一个值被选择得非常高(考虑到非常大的 N,这应该不太可能),那么所有其他值都会受到限制:

int prevValue = 0;
int maxRange;
for (int i = 0; i < M; ++i) {
    maxRange = N - (((M - 1) - i) * K) - prevValue;
    int nextValue = random(0, maxRange);
    prevValue += nextValue;
    store previous value;
    prevValue += K;
}

这仍然是线性和随机的,并且允许不均匀,但是prevValue 越大,其他数字就越受限制。就个人而言,我更喜欢我的第二个编辑答案,但这是一个可用的选项,给定足够大的 N 很可能满足所有发布的要求。

想一想,这是另一个想法。它需要更多的数据维护,但仍然是 O(M) 并且可能是最公平的分布:

您需要做的是维护有效数据范围的向量和概率尺度向量。有效数据范围只是 K 仍然有效的高低值列表。这个想法是您首先使用缩放概率来选择一个随机数据范围,然后在该范围内随机选择一个值。您删除旧的有效数据范围并将其替换为同一位置的 0、1 或 2 个新数据范围,具体取决于仍有多少有效。所有这些动作都是常数时间而不是处理加权概率,也就是O(M),循环完成M次,所以总数应该是O(M^2),应该比O(NlogN)好很多因为 N >> M。

让我使用 OP 的原始示例来做一个示例,而不是伪代码:

  • 第 0 次迭代:有效数据范围为 [0...100Mill],此范围的权重为 1.0。
  • 第一次迭代:在一个元素向量中随机选择一个元素,然后在该范围内随机选择一个元素。
    • 如果元素是,例如12345678,然后我们移除 [0...100Mill] 并将其替换为 [0...12344678] 和 [12346678...100Mill]
    • 如果元素是,例如500,然后我们删除 [0...100Mill] 并仅用 [1500...100Mill] 替换它,因为 [0...500] 不再是有效范围。我们将其替换为 0 范围的唯一一次是在不太可能的情况下,您的范围中只有一个数字并且它被选中。 (在这种情况下,您将连续拥有 3 个数字,它们之间的距离正好为 K。)
    • 范围的权重是它们的长度占总长度,例如12344678/(12344678 + (100Mill - 12346678)) 和 (100Mill - 12346678)/(12344678 + (100Mill - 12346678))

在接下来的迭代中,您会做同样的事情:随机选择一个介于 0 和 1 之间的数字,并确定比例尺属于哪个范围。然后在该范围内随机选择一个数字,并替换您的范围和比例。

当它完成时,我们不再以 O(M) 行动,但我们仍然只依赖于 M 而不是 N 的时间。这实际上是均匀且公平的分布。

希望这些想法之一对你有用!

【讨论】:

  • 这是一个有趣的解决方案,但它是否保证每个可能的组合都具有相同的生成概率?
  • '在 K 和 N/M 之间选择一个随机数' - 当 M 不能完全被 N 整除时,会不会导致对最后一个元素的偏差?
  • 由于 M = 3,我必须假设您的意思是 (1 5 9) 和 (1 5 10)。请注意,在这个例子中,我不认为我们是真正的“>>”(这很重要,因为分布问题甚至可以超过很大的差异),但你的例子确实提出了一些我将解决的极端情况在我的回答中。
  • 这些解决方案保证了随机分布所没有的分布的一定均匀性;您会期望结果中存在几倍 N/M 的差距。随机数的选择概率通常需要一致性,而不是它们的分布。
  • 这个解决方案不能为 N、M、K 的某些选择生成所有可能的解决方案集。例如,取 N=100,M=10000,K=10,那么可能的解决方案是10 到 1000 的所有倍数,但这种方法永远无法生成,因为它只生成范围 [1; 100],例如。
猜你喜欢
  • 2016-05-06
  • 2016-08-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-06
  • 1970-01-01
  • 2012-02-11
相关资源
最近更新 更多