快速生成随机紊乱
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] 随机数生成器