【问题标题】:Stepping through all permutations one swap at a time一次一个交换遍历所有排列
【发布时间】:2011-01-01 06:50:53
【问题描述】:

给定一个包含 n 个不同项目的列表,我如何逐步遍历项目的每个排列,一次只交换一对值? (我认为这是可能的,当然感觉应该是。)

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

示例:- 对于 3 个元素,您可以将最后一个元素与第一个和第二个元素交替交换以循环排列,即:(abc) swap 0-2 => (cba) 1-2 (cab) 0 -2 (bac) 1-2 (bca) 0-2 (acb)。

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

【问题讨论】:

标签: iterator permutation swap


【解决方案1】:

啊,一旦我计算出 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】:

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

    【讨论】:

      【解决方案3】:

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

      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 另一个收集各种实现的链接。

      【讨论】:

        【解决方案4】:

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

        【讨论】:

        • next_permutation 按字典顺序逐步完成排列,因此它不能一次交换一对元素。例如,按字典顺序 (a d c b) 后跟 (b a c d),这是单次交换无法实现的。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-04-12
        • 1970-01-01
        • 1970-01-01
        • 2015-10-14
        相关资源
        最近更新 更多