特别是在同一类型的一维项目集的域中,例如整数向量。
例如,假设您有一个大小为 32,768 的向量,其中包含从 0 到 32,767 的排序整数。
我所说的“下一个排列”的意思是在词汇排序系统中执行下一个排列。
维基百科列出了两个,我想知道是否还有更多(除了一些 bogo :P)
O(N) 实现 这是基于 Eyal Schneider 的映射 Zn!-> P(n)
def get_permutation(k, lst):
N = len(lst)
while N:
next_item = k/f(N-1)
lst[N-1], lst[next_item] = lst[next_item], lst[N-1]
k = k - next_item*f(N-1)
N = N-1
return lst
它通过将转换步骤与寻找排列相结合来减少他的 O(N^2) 算法。它本质上与 Fisher-Yates 具有相同的形式,但用映射的下一步替换了对 random 的调用。如果映射实际上是双射(我正在努力证明),那么这是一个比 Fisher-Yates 更好的算法,因为它只调用一次伪随机数生成器,因此效率更高。还要注意,这会返回置换 (N! - k) 而不是置换 k 的作用,但这没有什么影响,因为如果 k 在 [0, N!] 上是一致的,那么 N! -k。
旧答案
这与“下一个”排列的想法略有关系。如果项目可以很好地排序,那么可以在排列上构建字典顺序。这允许您从整数构造一个映射到排列空间。
那么找一个随机排列就相当于在0到N之间选择一个随机整数!并构造相应的排列。该算法将与计算所讨论的集合的第 n 个排列一样有效(并且难以实现)。如果我们对 n 的选择是一致的,这很容易给出一个一致的排列选择。
关于排序排列的更多细节。给定一个集合S = {a b c d}
,数学家将 的排列集合S
视为一个具有组合运算的群。如果p
是一个排列,假设(b a c d)
,然后p
通过S
将 b 到 a、a 到 c、c 到 d 和 d 到 b 来进行操作。ifq
是另一种排列,可以说(d b c a)
thenpq
是通过首先应用q
然后p
给出(d a b)(c)
. 例如,q
将 d 带到 b 并将p
b 带到 a 以便pq
将 d 带到 a。你会看到它pq
有两个周期,因为它需要 b 到 d 并修复 c。习惯上省略 1-cycles,但为了清楚起见,我将其保留。
我们将使用群论中的一些事实。
(a b)(c d)
是相同的(c d)(a b)
(a b c) = (b c a) = (c a b)
因此,给定一个排列,对循环进行排序,以便最大的循环首先出现。当两个循环的长度相同时,排列它们的项目,以便最大的(我们总是可以订购一个可数集,即使是任意的)项目在前。然后,我们首先对循环的长度进行字典排序,然后对它们的内容进行排序。这是有序的,因为由相同循环组成的两个排列必须是相同的排列,所以 ifp > q
和q > p
then p = q
。
该算法可以在 O(N!logN! + N!) 时间内轻松执行。只需构建所有排列(编辑:为了清楚起见,当我提出这个建议时,我戴着我的数学家帽子,无论如何它都是在开玩笑),快速排序并找到第 n 个。不过,它与您提到的两种算法不同。
这是一个关于如何改进aaronasterling答案的想法。它避免生成所有 N! 排列并根据它们的字典顺序对它们进行排序,因此具有更好的时间复杂度。
在内部,它使用了一种不寻常的排列表示,它模拟了从缩小数组中选择和删除的过程。例如,序列 <0,1,0> 表示从 [0,1,2] 中删除项目 #0,然后从 [1,2] 中删除项目 #1,然后从 [ 中删除项目 #0 得到的排列。 1]。得到的排列是 <0,2,1>。使用这种表示,第一个排列将始终为 <0,0,...0>,最后一个排列将始终为 <N-1,N-2,...0>。我将这种特殊表示称为“数组表示”。
显然,通过使用数组并在必要时缩小它,可以在 O(N^2) 时间内将大小为 N 的数组表示转换为标准排列表示。
以下函数可用于返回数组表示中 {0,1,2...,N-1} 上的第 K 个排列:
getPermutation(k, N) {
while(N > 0) {
nextItem = floor(k / (N-1)!)
output nextItem
k = k - nextItem * (N-1)!
N = N - 1
}
}
该算法在 O(N^2) 时间内工作(由于表示转换),而不是 O(N!log N) 时间。
- 例子 -
getPermutation(4,3) 返回 <2,0,0>。这个数组表示对应于 <C,A,B>,它实际上是 {A,B,C} 上排列的有序列表中索引 4 处的排列:
ABC
ACB
BAC
BCA
CAB
CBA
您可以调整合并排序,使其随机打乱输入而不是对其进行排序。
特别是,在合并两个列表时,您随机选择新的头元素,而不是选择它作为最小的头元素。从第一个列表中选择元素的概率必须是第一个列表n/(n+m)
的n
长度和m
第二个列表的长度,这样才能正常工作。
我在这里写了一个详细的解释:Random Permutations and Sorting。
另一种可能性是构建一个周期等于您想要的项目数的 LFSR 或 PRNG。
从排序数组开始。选择 2 个随机索引,在这些索引处切换元素。重复 O(n lg n) 次。
您需要重复 O(n lg n) 次以确保分布接近均匀。(您需要确保每个索引至少被挑选一次,这是一个垃圾箱问题。)