逐步完成所有排列交换

时间:2010-01-04 15:01:01

标签: iterator permutation swap

给定n个不同项目的列表,如何逐步交换每次交换一对值的项目的每个排列? (我认为这是可能的,它确实应该是这样。)

我正在寻找的是一个迭代器,它产生下一对要交换的项的索引,这样如果迭代n!-1次,它将逐步通过n!列表的排列按某种顺序排列。如果再次迭代它会将列表恢复到它的起始顺序,这将是一个奖励,但它不是一个要求。如果所有对都涉及第一个(相应的是最后一个)元素作为其中一个,那么该函数只需返回一个值,这也是一个奖励。

示例: - 对于3个元素,您可以交替地将最后一个元素与第一个和第二个元素交换以循环排列,即:(a b c)swap 0-2 => (c b a)1-2(c a b)0-2(b a c)1-2(b c a)0-2(a c b)。

我将在C中实现,但可能会解决大多数语言的解决方案。

4 个答案:

答案 0 :(得分:18)

我确定这对你来说已经太晚了,但我发现这个问题还有一个很好的补充:Steinhaus–Johnson–Trotter algorithm它的变体完全符合你的要求。此外,它具有额外的属性,它总是交换相邻的指数。我尝试在Java中实现其中一个变体(Even)作为迭代器并且运行良好:

import java.util.*;

// Based on https://en.wikipedia.org/wiki/Steinhaus%E2%80%93Johnson%E2%80%93Trotter_algorithm#Even.27s_speedup
public class PermIterator
    implements Iterator<int[]>
{
    private int[] next = null;

    private final int n;
    private int[] perm;
    private int[] dirs;

    public PermIterator(int size) {
        n = size;
        if (n <= 0) {
            perm = (dirs = null);
        } else {
            perm = new int[n];
            dirs = new int[n];
            for(int i = 0; i < n; i++) {
                perm[i] = i;
                dirs[i] = -1;
            }
            dirs[0] = 0;
        }

        next = perm;
    }

    @Override
    public int[] next() {
        int[] r = makeNext();
        next = null;
        return r;
    }

    @Override
    public boolean hasNext() {
        return (makeNext() != null);
    }

    @Override
    public void remove() {
        throw new UnsupportedOperationException();
    }

    private int[] makeNext() {
        if (next != null)
            return next;
        if (perm == null)
            return null;

        // find the largest element with != 0 direction
        int i = -1, e = -1;
        for(int j = 0; j < n; j++)
            if ((dirs[j] != 0) && (perm[j] > e)) {
                e = perm[j];
                i = j;
            }

        if (i == -1) // no such element -> no more premutations
            return (next = (perm = (dirs = null))); // no more permutations

        // swap with the element in its direction
        int k = i + dirs[i];
        swap(i, k, dirs);
        swap(i, k, perm);
        // if it's at the start/end or the next element in the direction
        // is greater, reset its direction.
        if ((k == 0) || (k == n-1) || (perm[k + dirs[k]] > e))
            dirs[k] = 0;

        // set directions to all greater elements
        for(int j = 0; j < n; j++)
            if (perm[j] > e)
                dirs[j] = (j < k) ? +1 : -1;

        return (next = perm);
    }

    protected static void swap(int i, int j, int[] arr) {
        int v = arr[i];
        arr[i] = arr[j];
        arr[j] = v;
    }


    // -----------------------------------------------------------------
    // Testing code:

    public static void main(String argv[]) {
        String s = argv[0];
        for(Iterator<int[]> it = new PermIterator(s.length()); it.hasNext(); ) {
            print(s, it.next());
        }
    }

    protected static void print(String s, int[] perm) {
        for(int j = 0; j < perm.length; j++)
            System.out.print(s.charAt(perm[j]));
        System.out.println();
    }
}

很容易将它修改为无限迭代器,它在最后重新启动循环,或者是一个迭代器,它将返回交换的索引而不是下一个排列。

Here收集各种实现的另一个链接。

答案 1 :(得分:4)

啊,一旦我计算了n = 4的序列(“总是将第一个项目与另一个项目交换”约束),我就能在OEIS中找到序列A123400,告诉我我需要“ Ehrlich的交换方法“。

Google找到了我a C++ implementation,我假设this是GPL。我也找到了Knuth的fascicle 2b,它描述了我的问题的各种解决方案。

一旦我有一个经过测试的C实现,我将用代码更新它。

这是一些基于Knuth描述实现Ehrlich方法的perl代码。对于最多10个项目的列表,我在每种情况下测试它是否正确生成了完整的排列列表然后停止了。

#
# Given a count of items in a list, returns an iterator that yields the index
# of the item with which the zeroth item should be swapped to generate a new
# permutation. Returns undef when all permutations have been generated.
#
# Assumes all items are distinct; requires a positive integer for the count.
#
sub perm_iterator {
    my $n = shift;
    my @b = (0 .. $n - 1);
    my @c = (undef, (0) x $n);
    my $k;
    return sub {
        $k = 1;
        $c[$k++] = 0 while $c[$k] == $k;
        return undef if $k == $n;
        ++$c[$k];
        @b[1 .. $k - 1] = reverse @b[1 .. $k - 1];
        return $b[$k];
    };
}

使用示例:

#!/usr/bin/perl -w
use strict;
my @items = @ARGV;
my $iterator = perm_iterator(scalar @items);
print "Starting permutation: @items\n";
while (my $swap = $iterator->()) {
    @items[0, $swap] = @items[$swap, 0];
    print "Next permutation: @items\n";
}
print "All permutations traversed.\n";
exit 0;

按要求,python代码。 (对不起,这可能不是过于惯用。欢迎提出改进建议。)

class ehrlich_iter:
  def __init__(self, n):
    self.n = n
    self.b = range(0, n)
    self.c = [0] * (n + 1)

  def __iter__(self):
    return self

  def next(self):
    k = 1
    while self.c[k] == k:
      self.c[k] = 0
      k += 1
    if k == self.n:
      raise StopIteration
    self.c[k] += 1
    self.b[1:k - 1].reverse
    return self.b[k]

mylist = [ 1, 2, 3, 4 ]   # test it
print "Starting permutation: ", mylist
for v in ehrlich_iter(len(mylist)):
  mylist[0], mylist[v] = mylist[v], mylist[0]
  print "Next permutation: ", mylist
print "All permutations traversed."

答案 2 :(得分:0)

查看C ++标准库函数next_permuation(...)。这应该是一个很好的起点。

答案 3 :(得分:0)

您可以查看https://sourceforge.net/projects/swappermutation/这是一个完全符合您要求的Java实现:一个生成Swaps的迭代器。前段时间创建了最近更新的。