Пошаговое выполнение всех перестановок по одной подкачке за раз

Имея список из n различных элементов, как я могу выполнить каждую перестановку элементов, меняя местами только одну пару значений за раз? (Я предполагаю, что это возможно, мне кажется, что так и должно быть.)

Я ищу итератор, который выдает индексы следующей пары элементов для замены, так что при повторении n!-1 раз он пройдет через n! перестановки списка в некотором порядке. Если повторение еще раз вернет список к его начальному порядку, это будет бонусом, но не требованием. Если все пары включают первый (соответственно последний) элемент как один из пары, так что функция должна возвращать только одно значение, это также будет бонусом.

Пример: - для 3 элементов вы можете поменять местами последний элемент попеременно с первым и вторым элементами, чтобы перебрать перестановки, а именно: (abc) swap 0-2 => (cba) 1-2 (cab) 0-2 ( bac) 1-2 (bca) 0-2 (acb).

Я буду реализовывать на C, но, вероятно, смогу найти решения для большинства языков.


person Hugo van der Sanden    schedule 04.01.2010    source источник
comment
Спросите stackoverflow.com/users/91671/lbushkin   -  person Hamish Grubijan    schedule 04.01.2010


Ответы (4)


Я уверен, что для вас уже слишком поздно, но я нашел хорошее дополнение к этому вопросу: Алгоритм Штейнхауса-Джонсона-Троттера и его варианты делают именно то, что вы просили. Кроме того, у него есть дополнительное свойство, заключающееся в том, что он всегда меняет местами соседние индексы. Я попытался реализовать один из вариантов (Even's) на 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();
    }
}

Было бы легко изменить его на бесконечный итератор, который перезапускает цикл в конце, или итератор, который будет возвращать замененные индексы вместо следующей перестановки.

Здесь еще одна ссылка, на которой собраны различные реализации.

person Petr    schedule 11.08.2012

Ах, как только я вычислил последовательность для n=4 (с всегда заменой первого элемента другим ограничением), я смог найти последовательность A123400 в OEIS, где мне сказали, что мне нужен метод подкачки Эрлиха.

Google нашел мне реализацию C++, которую я предполагаю from это находится под лицензией GPL. Я также нашел выпуск 2b Кнута, в котором описаны различные решения именно моей проблемы.

Как только у меня будет протестированная реализация C, я обновлю ее кодом.

Вот некоторый код 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."
person Hugo van der Sanden    schedule 05.01.2010

Взгляните на стандартную библиотечную функцию C++ next_permuation(...). Это должно быть хорошей отправной точкой.

person MisterHH    schedule 04.01.2010
comment
next_permutation выполняет перестановки в лексикографическом порядке, поэтому он не может менять местами одну пару элементов за раз. Например, лексикографически за (a d c b) следует (b a c d), чего нельзя добиться с помощью одной замены. - person Hugo van der Sanden; 04.01.2010

Вы можете взглянуть на https://sourceforge.net/projects/swappermutation/, который является Java реализация именно ваших требований: итератор, генерирующий свопы. Создал это некоторое время назад и недавно обновил.

person wasperen    schedule 13.10.2013