快速生成随机紊乱

Posted

技术标签:

【中文标题】快速生成随机紊乱【英文标题】:Fast generation of random derangements 【发布时间】:2020-05-23 16:18:44 【问题描述】:

我希望随机生成derangements。换句话说:打乱一个向量,以便没有元素留在原来的位置

要求:

均匀采样(每个紊乱以相同的概率生成) 实际实现比拒绝方法更快(即不断生成随机排列,直到我们发现紊乱)

到目前为止,我找到的答案都不是令人满意的,因为它们要么不均匀采样(或无法证明均匀性),要么没有与拒绝方法进行实际比较。大约1/e = 37% 的排列是混乱的,它提供了一个线索,即相对于拒绝方法,人们最多可以期望什么性能。

我发现的唯一一个进行实际比较的参考是this thesis,他们提出的算法基准为 7.76 秒,拒绝方法基准为 8.25 秒(参见第 73 页)。这仅是 1.06 倍的加速。我想知道是否有可能更好(> 1.5)。

我可以实现和验证论文中提出的各种算法,并对它们进行基准测试。正确地做到这一点需要相当多的时间。我希望有人做过,并且可以给我参考。

【问题讨论】:

你看过Fisher-Yates shuffle吗? @dbush 这是一种用于对所有排列进行均匀采样的算法,而不是用于采样紊乱的算法。这是我在问题中提到的“拒绝方法”的一部分。 我可以想到一种算法,但它需要一个辅助缓冲区。您在寻找无额外缓冲区算法吗? @acegs 我对在新缓冲区中构造混洗向量的算法感兴趣。那个应该以同样使用新缓冲区的 Fisher-Yates 为基准。 最好的事情实际上可能是拒绝方法的一种变体,一旦明确结果不会是混乱,它就会重新开始生成。此处对此进行了描述:***.com/a/25238398/695132 【参考方案1】:

令 d(n) 为长度为 n 的数组 A 的紊乱数。

d(n) = (n-1) * (d(n-1) + d(n-2))

d(n) 排列通过以下方式实现:

1. First, swapping A[0] with one of the remaining n-1 elements
2. Next, either deranging all n-1 remaning elements, or deranging 
   the n-2 remaining that excludes the index 
   that received A[0] from the initial matrix.

我们怎样才能随机均匀地产生一个紊乱?

1. Perform the swap of step 1 above.
2. Randomly decide which path we're taking in step 2,
   with probability d(n-1)/(d(n-1)+d(n-2)) of deranging all remaining elements.
3. Recurse down to derangements of size 2-3 which are both precomputed.

***有 d(n) = floor(n!/e + 0.5)(完全正确)。对于小 n,您可以使用它在恒定时间内精确计算步骤 2 的概率。对于较大的 n,阶乘可能会很慢,但您所需要的只是比率。大约是 (n-1)/n。您可以接受近似值,或者预先计算并将比率存储到您正在考虑的最大 n。

请注意,(n-1)/n 收敛速度非常快。

【讨论】:

【参考方案2】:

这里有一个可能适合您的算法的想法。生成循环符号的混乱。所以(1 2) (3 4 5) 代表混乱2 1 4 5 3。 (即(1 2)是一个循环,(3 4 5)也是。)

将第一个元素放在首位(在循环符号中,您总是可以这样做),然后对其余元素进行随机排列。现在我们只需要找出括号中循环长度的位置。

正如https://mathoverflow.net/questions/130457/the-distribution-of-cycle-length-in-random-derangement 所说,在排列中,随机循环的长度是均匀分布的。它们不是随机分布在混乱中的。但是长度m 的混乱数是m!/e 向上舍入为偶数 m 和向下舍入为奇数m。因此,我们可以做的是选择一个均匀分布在2..n 范围内的长度,并以剩余元素随机进行的概率为混乱接受它。该周期长度将正确分布。然后,一旦我们有了第一个周期长度,我们就重复下一个周期,直到完成。

按照我描述的方式完成的过程更易于实现,但在数学上等同于随机扰乱(通过拒绝),并只写下第一个周期。然后重复。因此有可能证明这会以相同的概率产生所有的紊乱。

天真地采用这种方法,在接受长度之前,我们将平均进行 3 次滚动。然而,我们随后将问题平均减少了一半。所以我们需要生成用于放置括号的随机数的数量是O(log(n))。与构造排列的O(n) 随机数相比,这是一个舍入误差。但是,可以通过注意接受的最高概率是0.5 来优化它。因此,如果我们以两倍的概率接受如果我们继续进行,那么我们的比率仍然是正确的,并且我们摆脱了我们对周期长度的大部分拒绝。

如果大部分时间都花在随机数生成器上,对于较大的n,这应该以大约 3 倍的拒绝方法运行。在实践中它不会那么好,因为从一种表示切换到另一种表示实际上并不是免费的。但是你应该得到你想要的数量级的加速。

【讨论】:

"如果大部分时间都花在随机数生成器上" 但是,这仍然以“将第一个元素放在第一位并对其余元素进行随机排列”开头,即创建一个随机排列,这需要O( n) 随机数,是算法的一部分。我们需要O(log(n)) 仅用于“放置括号”。还是我误会了? @Szabolcs 是的,这是正确的。这就是我想说的,“所以我们需要生成用于放置括号的随机数的数量是O(log(n))。与构建排列的O(n) 随机数相比,这是一个四舍五入错误。"【参考方案3】:

我很好奇......并且在数学上不了解。所以我天真地问,为什么“简单的洗牌”就不够了?

for i from array_size downto 1:  # assume zero-based arrays
  j = random(0,i-1)
    swap_elements(i,j)

由于random 函数永远不会产生等于i 的值,它永远不会离开它开始的元素。每个元素都将被移动到“其他地方”。

【讨论】:

这是由一个现已删除的答案提出的。我的要求是对异常进行统一采样。这意味着该算法应该以相同的概率(您的提案满足)产生每个可能的结果,并且它可以产生所有的混乱(它不满足)。它不能生成例如(2, 1, 4, 3) 来自(1 2 3 4) 网上有很多关于这个话题的帖子,也有很多这样的建议。通常,确定建议是否正确需要相当长的时间。这就是为什么我要求提供证明(或更现实地说,是对证明的引用)。【参考方案4】:

这只是一个想法,但我认为它会产生均匀分布的混乱。 但是您需要一个最大约为 N/2 个元素的辅助缓冲区,其中 N 是要排列的项目的大小。

首先是为值1 选择一个随机(1,N) 位置。 注意:为了简单起见,1 to N 而不是 0 to N-1。 那么对于值2,如果1 落在位置2 上,位置将为随机(1,N-1),否则为随机(1,N-2)。 算法将遍历列表并仅计算尚未使用的位置,直到它到达为值 2 选择的随机位置,当然会跳过位置 2。 对于值3,算法将检查位置3 是否已被使用。如果使用,pos3 = random(1,N-2),如果没有,pos3 = random(1,N-3) 再次,算法将遍历列表并仅计算尚未使用的位置,直到达到 count=pos3。然后将值 3 放在那里。 这将用于下一个值,直到将所有值完全放置在位置中。

这将产生一个统一的概率紊乱。

优化将集中在算法如何快速达到pos#。 除了遍历列表来计算尚未使用的位置,算法可以使用某种堆,例如搜索尚未使用的位置,而不是逐个计数和检查位置。或除了类似堆的搜索之外的任何其他方法.这是一个需要解决的单独问题:how to reached an unused item given it's position-count in a list of unused-items.

【讨论】:

哎呀,1 件事 1 没有考虑。如果随机化器在 N-1 个放置过程中没有生成第 N 个位置,则项目 N 将被放置在 posN 中,这不是紊乱。 您确定有这样的数据结构可以在O(1) 平均时间内完成插入(或删除)和查找吗?因为否则该算法的平均时间复杂度比使用 Fisher-Yates 的拒绝方法更差。 我没有说过任何关于使用特定搜索算法的内容。第一个目标是产生统一的概率。然后其他有助于实现优化的算法是不同的模块/助手算法。我们现在可能不知道是否有一天,有人会发现一个更快的辅助算法。它只是不知道现在存在,也许吧。

以上是关于快速生成随机紊乱的主要内容,如果未能解决你的问题,请参考以下文章

python 生成随机ASCII字符和数字的连续流。有用的快速生成随机文件。

矩阵(快速幂):COGS 963. [NOI2012] 随机数生成器

生成随机字符串序列的快速方法

需要一个用于 C++ 的快速随机生成器 [关闭]

SQLServer如何快速生成100万条不重复的随机8位数字

快速生成大量随机大小的文件