一度に1つずつスワップするすべての順列を踏む
-
18-09-2019 - |
質問
n個の異なるアイテムのリストが与えられた場合、一度に1ペアの値のみを交換するアイテムの各順列を踏み出すにはどうすればよいですか? (私はそれが可能だと思います、それは確かにそうあるべきだと感じています。)
私が探しているのは、次のアイテムのペアのインデックスをもたらすイテレーターです。これにより、n!-1倍の繰り返しがnを介して段階的になります!ある順序でリストの順列。それをもう一度繰り返すと、リストをボーナスになる開始命令に復元すると、それは要件ではありません。すべてのペアがペアの1つとして最初の(最終)要素を含む場合、関数が単一の値を返すだけである必要がある場合、それもボーナスになります。
例: - 3つの要素の場合、最後の要素を最初と2番目の要素と交互に交換して順列をループすることができます。つまり、ABC)スワップ0-2 =>(CBA)1-2(CAB)0-2( BAC)1-2(BCA)0-2(ACB)。
私はCで実装しますが、おそらくほとんどの言語でソリューションをパズルアウトすることができます。
解決 2
ああ、n = 4のシーケンスを計算したら(「最初のアイテムを常に別のアイテムと交換する」制約)、シーケンスを見つけることができました A123400 OEISでは、「Ehrlichのスワップ方法」が必要だと言っていました。
グーグルは私を見つけました C ++実装, 、私が想定しています これ GPLの下にあります。 Knuth'sも見つけました ファシクル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."
他のヒント
私はあなたには遅すぎると確信していますが、私はこの質問に素晴らしい追加を見つけました: Steinhaus – Johnson –Trotterアルゴリズム そして、そのバリエーションはあなたが求めたことを正確に行います。さらに、隣接するインデックスを常に交換する追加のプロパティがあります。私はJavaでバリアントの1つ(偶数)をイテレーターとして実装しようとし、うまく機能しようとしました。
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();
}
}
最後にサイクルを再起動する無限のイテレーター、または次の順列の代わりに交換されたインデックスを返すイテレーターに変更するのは簡単です。
ここ さまざまな実装を収集する別のリンク。
C ++標準ライブラリ関数next_permuation(...)をご覧ください。それは良い出発点であるはずです。
あなたは見ることができます https://sourceforge.net/projects/swappermutation/ これは、正確な要件のJava実装です。スワップを生成するイテレーターです。しばらく前にこれを作成しました。最近更新されました。