我想知道是否有一个足够简单的算法来生成 N 个元素的排列,比如说1..N
,它使用的内存少于O(N)
内存。它不必计算第 n 个排列,但它必须能够计算所有排列。
当然,这个算法应该是某种生成器,或者使用一些使用较少O(N)
内存的内部数据结构,因为将结果作为一个大小的向量返回N
已经违反了对亚线性内存的限制。
我想知道是否有一个足够简单的算法来生成 N 个元素的排列,比如说1..N
,它使用的内存少于O(N)
内存。它不必计算第 n 个排列,但它必须能够计算所有排列。
当然,这个算法应该是某种生成器,或者使用一些使用较少O(N)
内存的内部数据结构,因为将结果作为一个大小的向量返回N
已经违反了对亚线性内存的限制。
让我们假设随机排列一次生成一个条目。生成器的状态必须对剩余的元素集进行编码(运行到完成),因此,由于不能排除任何可能性,因此生成器状态至少为 n 位。
我认为即使存储您的结果(这将是 N 个项目的有序列表)在内存中也将是 O(N),不是吗?
无论如何,为了回答您稍后关于随机选择排列的问题,这里有一种比只产生所有 N 更好的技术!例如,列表中的可能性,然后随机选择一个索引。如果我们可以随机选择索引并从中生成相关的排列,我们会好得多。
我们可以想象您的单词/排列的字典顺序,并根据单词/排列在字典中的出现顺序将唯一编号与这些相关联。例如,三个字符上的单词将是
perm. index
012 <----> 0
021 <----> 1
102 <----> 2
120 <----> 3
201 <----> 4
210 <----> 5
稍后您会看到为什么使用我们所做的数字最容易,但其他数字可以通过更多的工作来适应。
要随机选择一个,您可以从 0 ... N!-1 范围内以均匀的概率随机选择其相关索引(我知道,即使是中等大的 N,最简单的实现显然也是不可能的,但是我认为有不错的解决方法),然后确定其相关的排列。请注意,列表以最后 N-1 个元素的所有排列开始,保持第一个数字固定等于 0。在这些可能性用尽之后,我们生成所有以 1 开头的那些。在这些之后(N-1)!排列已用尽,我们生成以 2 开头的排列。等等。因此我们可以确定前导数字是 Floor[R / (N-1)!],其中 R 是上面所示意义上的索引。现在看看为什么我们也将索引归零?
为了生成排列中剩余的 N-1 位,假设我们确定了 Floor[R/(N-1)!] = a0。从列表 {0, ..., N-1} - {a0} 开始(设置减法)。我们想要这个列表的第 Q 个排列,因为 Q = R mod (N-1)!。除了考虑到缺少数字这一事实外,这与我们刚刚解决的问题相同。递归。
也许你可以,用阶乘数。您可以逐步从中提取结果排列,因此您不必将整个结果保存在内存中。
但我可能开始的原因是,我不确定阶数本身大小的增长行为是什么。如果它适合一个 32 位整数或类似的东西,N 将被限制为一个常数,所以 O(N) 将等于 O(1),所以我们必须为它使用一个数组,但我不确定它会有多大以 N 表示。
我认为答案必须是“不”。
将 N 元素排列的生成器视为状态机:它必须至少包含与排列一样多的状态,否则它将在完成所有排列之前开始重复。
有N个!这样的排列,至少需要 ceil(log2(N!)) 位来表示。 斯特林的近似值告诉我们 log2(N!) 是 O(N log N),所以我们将无法创建这样一个具有亚线性内存的生成器。
C++ 算法next_permutation
对序列进行就地重排,使其进入下一个排列,或者false
在不存在进一步排列时返回。算法如下:
template <class BidirectionalIterator>
bool next_permutation(BidirectionalIterator first, BidirectionalIterator last) {
if (first == last) return false; // False for empty ranges.
BidirectionalIterator i = first;
++i;
if (i == last) return false; // False for single-element ranges.
i = last;
--i;
for(;;) {
BidirectionalIterator ii = i--;
// Find an element *n < *(n + 1).
if (*i <*ii) {
BidirectionalIterator j = last;
// Find the last *m >= *n.
while (!(*i < *--j)) {}
// Swap *n and *m, and reverse from m to the end.
iter_swap(i, j);
reverse(ii, last);
return true;
}
// No n was found.
if (i == first) {
// Reverse the sequence to its original order.
reverse(first, last);
return false;
}
}
}
这对生成的每个排列使用常量空间(迭代器)。你认为是线性的吗?