Algorithm 用次线性记忆生成置换

Algorithm 用次线性记忆生成置换,algorithm,complexity-theory,generator,permutation,memory-efficient,Algorithm,Complexity Theory,Generator,Permutation,Memory Efficient,我想知道是否有一个足够简单的算法来生成N个元素的排列,比如1..N,它使用的内存少于O(N)。它不必计算第n个置换,但必须能够计算所有置换 当然,这个算法应该是某种生成器,或者使用一些使用少于O(N)内存的内部数据结构,因为作为大小N的向量返回结果已经违反了对亚线性内存的限制。我认为答案必须是“否” 将N元素置换生成器视为一个状态机:它必须至少包含与置换相同数量的状态,否则它将在完成所有置换生成之前开始重复 有N个!这种置换至少需要ceil(log2(N!)位来表示。告诉我们log2(N!)是O

我想知道是否有一个足够简单的算法来生成N个元素的排列,比如
1..N
,它使用的内存少于
O(N)
。它不必计算第n个置换,但必须能够计算所有置换


当然,这个算法应该是某种生成器,或者使用一些使用少于
O(N)
内存的内部数据结构,因为作为大小
N
的向量返回结果已经违反了对亚线性内存的限制。

我认为答案必须是“否”

将N元素置换生成器视为一个状态机:它必须至少包含与置换相同数量的状态,否则它将在完成所有置换生成之前开始重复


有N个!这种置换至少需要ceil(log2(N!)位来表示。告诉我们log2(N!)是O(N logn),因此我们将无法创建这样一个具有次线性内存的生成器。

让我们假设随机排列一次生成一个条目。生成器的状态必须对剩余的元素集进行编码(运行到完成),因此,由于没有排除的可能性,生成器状态至少为N位。

< P> C++算法<代码> NExtx置换> /CODE>执行序列的适当排列到其下一排列中,或在不存在进一步排列时返回
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;
        }
    }
}
模板
布尔下一个置换(首先是双向运算符,最后是双向运算符){
if(first==last)返回false;//空范围为false。
双向运算符i=第一个;
++一,;
if(i==last)返回false;//对于单个元素范围为false。
i=最后;
--一,;
对于(;;){
双向算子ii=i--;
//查找一个元素*n<*(n+1)。
如果(*i=*n。
而(!(*i<*--j)){}
//交换*n和*m,并从m反转到末尾。
国际热核实验堆交换(i,j);
反面(二,最后一个);
返回true;
}
//未发现任何n。
如果(i==第一个){
//将顺序颠倒到原来的顺序。
反向(第一,最后);
返回false;
}
}
}

使用每个生成的置换的常量空间(迭代器)。你认为线性?

< P>我认为即使存储你的结果(这将是n个项目的有序列表)在内存中也将是O(n),不是吗?< /P> 无论如何,为了回答你后面关于随机选择排列的问题,这里有一种技术,它比在一个列表中生成所有N!个可能性,然后随机选择一个索引要好得多。如果我们可以随机选择索引并从中生成相关的排列,我们会过得更好

我们可以想象单词/排列的字典顺序,并根据单词/排列在字典中的出现顺序将唯一的数字与它们关联。例如,三个字符上的单词将是

   perm.              index
    012    <---->       0
    021    <---->       1
    102    <---->       2
    120    <---->       3
    201    <---->       4
    210    <---->       5
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}(集减法)开始。我们需要这个列表的Qth置换,对于Q=R mod(N-1)!.除了考虑缺少一个数字这一事实之外,这与我们刚刚解决的问题是一样的。递归。

也许你可以,使用factoradic数字。你可以一步一步地从中提取结果排列,这样你就不必在内存中存储整个结果


但我开始的原因可能是,我不确定factoradic数本身大小的增长行为是什么。如果它适合32位整数或类似的东西,N将被限制为一个常数,因此O(N)将等于O(1),所以我们必须使用一个数组,但我不确定它在N方面会有多大。

除非元素对它们有一个预定义的顺序,例如int,这样您就可以在需要时生成下一个,否则不只是接受N个元素作为输入就已经将您置于O(N)了吗内存限制?是的,这就是为什么我说元素是'1..N',所以它们不需要作为输入向量传递。这是怎么回事?O(1)在空间中,但运行时可能是无界的:
Random rnd=…;List l=…;while(l.size()
因为有一个使用n+O(logn)位的生成器,所以这个界基本上是紧的。这一点很好。但是如果我对概率分布均匀的随机置换感兴趣呢?是的。但是有可能生成O(n)位的随机置换吗内存。分布应该是均匀的,否则可以简单地生成1..N,这是一个有效的排列?以便