【问题标题】:Generating all permutations excluding cyclic rotations生成不包括循环旋转的所有排列
【发布时间】:2012-02-20 03:28:03
【问题描述】:

所以我需要一种算法来生成不包括循环旋转的数字列表的所有排列(例如 [1,2,3] == [2,3,1] == [3,1,2])。

当序列中至少有 1 个唯一数字时,这相当简单,取出该唯一数字,生成剩余数字的所有排列(但对“标准”排列算法稍作修改)并添加前面的唯一编号。

为了生成排列,我发现有必要将排列代码更改为:

def permutations(done, options)
    permuts = []
    seen = []
    for each o in options
        if o not in seen
            seen.add(o)
            permuts += permutations(done+o, options.remove(o))
    return permuts

仅使用选项中的每个唯一数字一次意味着您不会获得 322 两次。

当没有唯一元素时,该算法仍会输出旋转,例如对于 [1,1,2,2],它将输出 [1,1,2,2]、[1,2,2,1] 和 [1,2,1,2],前两个是循环旋转。

那么有没有一种有效的算法可以让我生成所有的排列,而不必经过之后去消除循环旋转?

如果不是,那么消除循环旋转的最有效方法是什么?

注意:这不是使用 Python,而是使用 C++。

【问题讨论】:

标签: c++ algorithm rotation permutation


【解决方案1】:

对于所有数字都不同的排列,这很简单。假设数字是1,2,...,n,然后生成1,2,...,n-1 的所有排列并将n 粘贴在前面。这给出了全套模循环旋转的所有排列。例如,使用n=4,你会这样做

4 1 2 3
4 1 3 2
4 2 1 3
4 2 3 1
4 3 1 2
4 3 2 1

这确保1,2,3,4 的每个排列的一些循环旋转在列表中只出现一次。

对于需要多集排列(允许重复条目)的一般情况,您可以使用类似的技巧。删除最大字母n 的所有实例(类似于在上面的示例中忽略4)并生成剩余多重集的所有排列。下一步是以规范的方式将ns 放回排列中(类似于在上面的示例中将4 放在开头)。

这其实只是一个找所有Lyndon words生成necklaces的例子

【讨论】:

  • 不知道它们被称为项链,这可能会使研究更容易,谢谢。
【解决方案2】:

考虑测试您输出的每个排列,寻找比您已有的“在词汇上”更早的循环旋转。如果有,请不要返回它 - 它会在其他地方被枚举。

选择“唯一”的第一个元素(如果存在)有助于优化。你知道如果你修复了第一个元素,并且它是独一无二的,那么你不可能通过旋转复制它。另一方面,如果没有这种独特的元素,只需选择出现最少的元素。这样,您只需要检查具有第一个元素的循环旋转。 (例如,当您生成 [1,2,2,1] - 您只需要检查 [1,1,2,2],而不是 [2,2,1,1] 或 [2,1,1,2 ])。


好的,伪代码......显然是 O(n!),我相信没有更聪明的方法,因为“所有符号不同”的情况显然必须返回 (n-1)!元素。

// generate all permutations with count[0] 0's, count[1] 1's...
def permutations(count[])
    if(count[] all zero)
        return just the empty permutation;
    else
        perms = [];
        for all i with count[i] not zero
            r = permutations(copy of count[] with element i decreased);
            perms += i prefixed on every element of r
        return perms;

// generate all noncyclic permutations with count[0] 0's, count[1] 1's...
def noncyclic(count[])
    choose f to be the index with smallest count[f];
    perms = permutations(copy of count[] with element f decreased);
    if (count[f] is 1)
        return perms;
    else
        noncyclic = [];
        for each perm in perms
            val = perm as a value in base(count.length);
            for each occurence of f in perm
                test = perm rotated so that f is first
                tval = test as a value in base(count.length);
                if (tval < val) continue to next perm;
            if not skipped add perm to noncyclic;
        return noncyclic;

// return all noncyclic perms of the given symbols
def main(symbols[])
    dictionary = array of all distinct items in symbols;
    count = array of counts, count[i] = count of dictionary[i] in symbols
    nc = noncyclic(count);
    return (elements of nc translated back to symbols with the dictionary)

【讨论】:

  • 检查它是否是排列函数中的“最小”旋转而不是 noncyclec 是否会更有效(至少在内存方面),所以你不必存储这么多在烫发,或者收益几乎可以忽略不计?
  • 你必须通过递归一直向下传递状态......并且能够进行测试,例如“因为我的第一个 f 后面跟着一个 x,请确保我添加的任何其他 f后跟 x 或更大”。看起来很艰难。
  • 我不确定你传递状态是什么意思,当我编写一个快速测试并有一个找到“最小”旋转和将其与当前排列进行比较
【解决方案3】:

这个解决方案将涉及到一些itertools.permutations 的使用、set() 和一些好的老式设置差异。请记住,寻找排列的运行时间仍然是 O(n!)。我的解决方案也不会在线执行,但可能有一个更优雅的解决方案允许您这样做(并且不涉及itertools.permutations)。为此,这是完成任务的直接方法。

第 1 步:使用给定的第一个元素生成循环的算法。对于列表[1, 1, 2, 2],这将为我们提供[1, 1, 2, 2], [1, 2, 2, 1], [2, 1, 1, 2], [2, 2, 1, 1]

def rotations(li):
    count = 0
    while count < len(li):
        yield tuple(li)
        li = li[1:] + [li[0]]
        count += 1

第 2 步:导入 itertools.permutations 以首先为我们提供排列,然后将其结果设置为 set

from itertools import permutations
perm = set(permutations([1, 1, 2, 2]))

第 3 步:使用生成器为我们提供我们自己的集合,以及循环(我们想要摆脱的东西)。

cycles = set(((i for i in rotations([1, 1, 2, 2]))))

第 4 步:对每个应用设置差异并删除循环。

perm = perm.difference(cycles)

希望这会对您有所帮助。我愿意接受建议和/或更正。

【讨论】:

  • 嗯,当我运行代码而不是 (1,1,2,2) 和 (1,2,1,2) 时,似乎返回 set([(1, 2, 1, 2), (2, 1, 2, 1)])到 python,因为我实际上是用 C++ 编写的
  • 不知道为什么要包含 Python 标签。老实说,有点误导。可以进行一些修改以提供所需的输出,但这或多或少是一块垫脚石,或者是开始的东西。
  • 好的,是的,请参阅我在原始问题上添加的关于 python 标签的注释(我没有添加它,但删除它会破坏所有突出显示,不确定是否有解决方法)
【解决方案4】:

首先我将展示我们将成为 using 的容器和算法:

#include <vector>
#include <set>
#include <algorithm>
#include <iostream>
#include <iterator>

using std::vector;
using std::set;
using std::sort;
using std::next_permutation;
using std::copy;
using std::ostream_iterator;
using std::cout;

接下来是我们的vector,它将代表Permutation

typedef vector<unsigned int> Permutation;

我们需要一个比较对象来检查一个排列是否是一个旋转:

struct CompareCyclicPermutationsEqual
{
    bool operator()(const Permutation& lhs, const Permutation& rhs);
};

typedef 一个set 使用循环比较对象:

typedef set<Permutation, CompareCyclicPermutationsEqual> Permutations;

那么main函数就很简单了:

int main()
{
    Permutation permutation = {1, 2, 1, 2};
    sort(permutation.begin(). permutation.end());

    Permutations permutations;

    do {
        permutations.insert(permutation);
    } while(next_permutation(numbers.begin(), numbers.end()))


    copy(permutations.begin(), permutations.end(),
         ostream_iterator<Permutation>(cout, "\n");

    return 0;
}

输出:

1, 1, 2, 2,
1, 2, 1, 2,

我还没有实现CompareCyclicPermutationsEqual。你还需要实现ostream&amp; operator&lt;&lt;(ostream&amp; os, const Permutation&amp; permutation)

【讨论】:

  • 我喜欢这很简单,但不是有点慢吗?我不知道 stl 集是如何工作的那么好,但我认为它是一个自平衡 BST,所以插入集的不是 O(L^2 log n) 吗?或者您可以使用不同的比较方法将其降低到 O(L log n)(我想我遇到了一个 O(n) 用于旋转比较的算法但找不到它)?
  • 这是该页面上最快的 C++ 实现(c:
  • 我认为在集合的末尾插入会让事情变得更好。今晚会考虑一下。
  • 在集合末尾插入是什么意思?它不是一直插入 BST 吗?
  • @user1172530 您可以给出一个提示,以便它开始比较应该插入的位置。由于该集合按字典顺序排序,并且排列顺序相同,因此以end 作为提示插入会更有效。
猜你喜欢
  • 2014-03-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多