【问题标题】:Uniform sampling of k integers from [0:n)从 [0:n) 中对 k 个整数进行均匀采样
【发布时间】:2017-08-18 17:28:24
【问题描述】:

我的目标是从 0, ... n-1 中不重复地抽取 k 个整数。采样整数的顺序无关紧要。在每次调用时(经常发生),n 和 k 会略有不同,但变化不大(n 约为 250,000,k 约为 2,000)。我想出了以下摊销 O(k) 算法:

  1. 准备一个包含项目 0、1、2、...、n-1 的数组 A。这需要 O(n),但由于 n 相对稳定,因此可以使成本摊销为常数。
  2. 从 [0:i] 中采样一个随机数 r,其中 i = n - 1。这里的成本实际上与 n 相关,但由于 n 不是非常大,因此这种依赖性并不重要。
  3. 交换数组 A 中的第 r 项和第 i 项。
  4. 将 i 减 1。
  5. 重复k次步骤2~4;现在我们在 A 的尾部有一个长度为 k 的随机排列。复制这个。
  6. 我们应该将 A 回滚到其初始状态 (0, ... , n-1) 以保持步骤 1 的成本不变。这可以通过在步骤 2 的每次通过时将 r 推送到长度为 k 的堆栈来完成。堆栈的准备需要摊销的恒定成本。

我认为排列/组合的统一采样应该是一个详尽研究的问题,所以要么 (1) 有一个更好的解决方案,要么至少 (2) 我的解决方案是一个众所周知的解决方案(稍作修改) .因此,

  • 在情况 (1) 中,我想知道更好的解决方案。
  • 情况(2),我想找一个参考。

请帮助我。谢谢。

【问题讨论】:

  • 你的方法有点像费雪-耶茨洗牌。不清楚——为什么需要回滚?
  • ad 1. 如果 n 是有界的,那么您只需要创建一次数组(前提是您最后回到订单,这是您的建议);广告 2. 我看不出它与 n 有什么关系,取一个范围内的随机数是 O(1);广告 6. 您不需要单独的堆栈,因为您在数组末尾有解决方案。
  • @Mbo 回滚是 O(k) 方法来“重新创建”一个大小为 n 的有序数组
  • @Sopel 关于 2:我不确定它是否真的需要一些成本,但我认为如果需要“真正均匀随机”采样,那么可能会有一些与 n 相关的成本。而且,您能解释一下如何使用创建的排列回滚吗?
  • @MBo 谢谢!根据维基百科,我的方法似乎被称为 Fisher-Yates shuffle 的“Durstenfeld 版本”。

标签: algorithm random uniform-distribution


【解决方案1】:
  1. 如果kn 小很多——比如说,不到n 的一半——那么最有效的解决方案是将生成的数字保存在一个哈希表中(实际上是一个哈希设置,因为没有与键关联的值)。如果随机数恰好已经在哈希表中,则拒绝它并在其位置生成另一个。使用建议的 kn 的实际值 (k ∼ 2000; n ∼ 250,000),生成 k 唯一样本的预期拒绝数少于 10,因此几乎不会引起注意。哈希表的大小为O(k),在样本生成结束时可以简单地删除。

  2. 还可以使用哈希表而不是n 值的向量来模拟 FYK shuffle 算法,从而避免不得不拒绝生成的随机数。如果您使用的是向量A,则首先将A[i] 初始化为i,对于每个0 ≤ i < k。对于哈希表H,您从一个空的哈希表开始,并使用约定H[i] 被认为是i,如果键i 不在哈希表中。算法中的第 3 步——“用A[i] 交换A[r]”——变成“添加H[r] 作为样本的下一个元素并将H[r] 设置为H[i]”。请注意,没有必要设置H[i],因为该元素将永远不会被再次引用:所有后续随机数r都是从不包括i的范围中生成的。

    因为这种情况下的哈希表同时包含键和值,它比上面备选方案 1 中使用的哈希集更大,并且增加的大小(以及随之而来的内存缓存未命中数增加)可能会导致比通过消除拒绝来保存。但是,即使k 偶尔接近n,它也能正常工作。

  3. 最后,在您提出的算法中,实际上很容易在 O(k) 时间内恢复 A。只有在以下情况下,算法才会修改值 A[j]

    一个。 n − k ≤ j < n,或

    b.有一些i 这样n − k ≤ i < nA[i] = j

    因此,您可以通过查看n − k ≤ i < n 的每个A[i] 来恢复向量A:首先,如果A[i] < n−k,将A[A[i]] 设置为A[i];然后,无条件地将A[i] 设置为i

【讨论】:

  • 1.我看不到使用哈希表而不是向量的任何优势。对于方法1,我认为最好使用标志向量。事实上,它比小 k 的 FYK 风格算法快,但预期的拒绝数是(略多于)k(k-1)/2(n-1),因此它确实增长得相当快。此外,它更依赖于随机数生成的性能,如果需要真正的随机行为,这可能会非常昂贵。对于方法 2,我仍然找不到哈希表提供的更好的功能。
  • 2.您在 3 中描述的内容似乎非常好。此外,正如我在问题中所说,我不关心生成顺序(组合而不是排列),因此如果 k 大于 n/2,则方法 1 仍然可以应用于 (n,n-k) 而不是 ( ñ,ķ)。因此,最好的可能是:(1)如果 k 非常小,则使用方法 1(2)如果 k 非常大,则使用方法 1 和 n-k(3)否则使用方法 2。谢谢!
  • 我做了一个简单的实验......看起来方法2对k的依赖性对于固定比率k / n比方法1更苛刻,尽管理论上两者都应该线性依赖于k当 k/n 固定时。正如您所说,增加缓存未命中的机会是其中的一个重要因素。
  • @junekey:对于适中的n 值,位向量将比散列集执行得更好;我应该提到这一点。哈希表的优点是它的大小是 O(k) 而不是 O(n);对于大的n(例如,64 位数字),这变得很重要。但如果你知道n 不是太大,我同意。
  • @junekey,最后:拒绝采样的成本是 O(k),因为 k*k/n 小于 k,假设 k/n 小于 1。(但是,那个近似值如果 k 接近 n,则无效。)如果 k 为 n/2,则第 k 个值的预期拒绝数恰好为 1;显然,早期值的预期拒绝次数较少。因此,如果您知道 k 不超过 n/2,拒绝抽样将需要少于两倍的随机数。但从好的方面来说,它们都在相同的范围内,可以节省一点。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-18
  • 1970-01-01
  • 2019-09-16
  • 1970-01-01
  • 2011-03-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多