Сгенерирование перестановок лениво

Я ищу алгоритм для создания перестановок набора таким образом, чтобы я мог составить ленивый список из них в Clojure. т.е. я хотел бы перебирать список перестановок, где каждая перестановка не вычисляется до тех пор, пока я ее не запрошу, и все перестановки не должны быть сохранены в памяти сразу.

В качестве альтернативы я ищу алгоритм, в котором задан определенный набор, он вернет "следующую" перестановку этого набора таким образом, что повторное вызов функции на своем собственном выходе будет циклически перемещаться по всем перестановкам оригинала set, в некотором порядке (какой порядок не имеет значения).

Есть ли такой алгоритм? Большинство алгоритмов генерации перестановок, которые я видел, имеют тенденцию генерировать их сразу (обычно рекурсивно), что не масштабируется до очень больших наборов. Реализация в Clojure (или другом функциональном языке) была бы полезна, но я могу понять ее из псевдокода.

Ответ 1

Да, есть алгоритм "следующей перестановки", и это тоже довольно просто. Стандартная библиотека шаблонов С++ (STL) даже имеет функцию под названием next_permutation.

Алгоритм фактически находит следующую перестановку - лексикографически следующую. Идея такова: предположим, вам дана последовательность, скажем, "32541". Что такое следующая перестановка?

Если вы подумаете об этом, вы увидите, что это "34125". И ваши мысли были, вероятно, что-то такое: в "32541",

  • нет возможности сохранить "32" исправлено и найти более позднюю перестановку в части "541" , потому что эта перестановка уже является последней для 5,4, а 1 - сортируется в порядке убывания.
  • Итак, вам нужно будет изменить "2" на нечто большее - на самом деле, до наименьшего числа, большего, чем в части "541" , а именно: 4.
  • Теперь, когда вы решили, что перестановка начнется с "34", остальные цифры должны быть в порядке возрастания, поэтому ответ будет "34125".

Алгоритм должен точно реализовать эту линию рассуждений:

  • Найдите самый длинный "хвост", который упорядочен в порядке убывания. (Часть "541" .)
  • Измените число перед хвостом ( "2" ) до наименьшего числа, большего, чем в хвосте (4).
  • Сортировка хвоста в порядке возрастания.

Вы можете сделать (1.) эффективно, начиная с конца и идти назад, пока предыдущий элемент не меньше текущего элемента. Вы можете сделать (2.), просто заменив "4" на "2", так что у вас будет "34521". После этого вы можете избежать использования алгоритма сортировки для (3.), потому что хвост был и остается (подумайте об этом), отсортированный в порядке убывания, поэтому его нужно только отменить.

Код С++ делает именно это (посмотрите на источник в /usr/include/c++/4.0.0/bits/stl_algo.h в вашей системе или посмотрите в этой статье); его нужно просто перевести на ваш язык: [Читайте "BidirectionalIterator" как "указатель", если вы не знакомы с итераторами С++. Код возвращает false, если нет следующей перестановки, т.е. Мы уже в порядке убывания.]

template <class BidirectionalIterator>
bool next_permutation(BidirectionalIterator first,
                      BidirectionalIterator last) {
    if (first == last) return false;
    BidirectionalIterator i = first;
    ++i;
    if (i == last) return false;
    i = last;
    --i;
    for(;;) {
        BidirectionalIterator ii = i--;
        if (*i <*ii) {
            BidirectionalIterator j = last;
            while (!(*i <*--j));
            iter_swap(i, j);
            reverse(ii, last);
            return true;
        }
        if (i == first) {
            reverse(first, last);
            return false;
        }
    }
}

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

Хорошо, что алгоритм работает, даже если у вас есть последовательность с повторяющимися элементами: с, скажем, "232254421", он найдет хвост как "54421", замените "2" и "4" (так "232454221" ), переверните остальные, указав "232412245", что является следующей перестановкой.

Ответ 2

Предполагая, что мы говорим о лексикографическом порядке над переставляемыми значениями, вы можете использовать два общих подхода:

  • преобразует одну перестановку элементов в следующую перестановку (как отправлено ShreevatsaR) или
  • непосредственно вычислить перестановку n th, при подсчете n от 0 вверх.

Для тех, кто (как я;-), которые не говорят С++ как туземцы, подход 1 может быть реализован из следующего псевдокода, предполагая нулевую индексацию массива с индексом нуль на "левом" (заменяя другая структура, такая как список, "оставлена ​​как упражнение"; -):

1. scan the array from right-to-left (indices descending from N-1 to 0)
1.1. if the current element is less than its right-hand neighbor,
     call the current element the pivot,
     and stop scanning
1.2. if the left end is reached without finding a pivot,
     reverse the array and return
     (the permutation was the lexicographically last, so its time to start over)
2. scan the array from right-to-left again,
   to find the rightmost element larger than the pivot
   (call that one the successor)
3. swap the pivot and the successor
4. reverse the portion of the array to the right of where the pivot was found
5. return

Здесь пример, начинающийся с текущей перестановки CADB:

1. scanning from the right finds A as the pivot in position 1
2. scanning again finds B as the successor in position 3
3. swapping pivot and successor gives CBDA
4. reversing everything following position 1 (i.e. positions 2..3) gives CBAD
5. CBAD is the next permutation after CADB

Для второго подхода (прямое вычисление n -й перестановки) помните, что существуют N! перестановки элементов n. Поэтому, если вы переставляете элементы n, первые перестановки (N-1)! должны начинаться с наименьшего элемента, следующие перестановки (N-1)! должны начинаться со второго наименьшего значения и так далее. Это приводит к следующему рекурсивному подходу (опять же в псевдокоде, нумерации перестановок и позиций из 0):

To find permutation x of array A, where A has N elements:
0. if A has one element, return it
1. set p to ( x / (N-1)! ) mod N
2. the desired permutation will be A[p] followed by
   permutation ( x mod (N-1)! )
   of the elements remaining in A after position p is removed

Итак, например, 13-я перестановка ABCD находится следующим образом:

perm 13 of ABCD: {p = (13 / 3!) mod 4 = (13 / 6) mod 4 = 2; ABCD[2] = C}
C followed by perm 1 of ABD {because 13 mod 3! = 13 mod 6 = 1}
  perm 1 of ABD: {p = (1 / 2!) mod 3 = (1 / 2) mod 2 = 0; ABD[0] = A}
  A followed by perm 1 of BD {because 1 mod 2! = 1 mod 2 = 1}
    perm 1 of BD: {p = (1 / 1!) mod 2 = (1 / 1) mod 2 = 1; BD[1] = D}
    D followed by perm 0 of B {because 1 mod 1! = 1 mod 1 = 0}
      B (because there only one element)
    DB
  ADB
CADB

Кстати, "удаление" элементов может быть представлено параллельным массивом булевых, который указывает, какие элементы все еще доступны, поэтому нет необходимости создавать новый массив для каждого рекурсивного вызова.

Итак, чтобы перебрать перестановки ABCD, просто посчитайте от 0 до 23 (4! -1) и непосредственно вычислите соответствующую перестановку.

Ответ 3

Вы должны проверить статью Permutations о википеде. Кроме того, существует концепция Factoradic.

Во всяком случае, математическая проблема довольно сложная.

В C# вы можете использовать iterator и остановить алгоритм перестановки с помощью yield. Проблема в том, что вы не можете идти туда и обратно или использовать index.

Ответ 4

Дополнительные примеры алгоритмов перестановок для их создания.

Источник: http://www.ddj.com/architect/201200326

  • Использует алгоритм Fike, который является самым быстрым из известных.
  • Использует Алго в Лексографическом порядке.
  • Использует негексографию, но работает быстрее, чем элемент 2.

1.


PROGRAM TestFikePerm;
CONST marksize = 5;
VAR
    marks : ARRAY [1..marksize] OF INTEGER;
    ii : INTEGER;
    permcount : INTEGER;

PROCEDURE WriteArray;
VAR i : INTEGER;
BEGIN
FOR i := 1 TO marksize
DO Write ;
WriteLn;
permcount := permcount + 1;
END;

PROCEDURE FikePerm ;
{Outputs permutations in nonlexicographic order.  This is Fike.s algorithm}
{ with tuning by J.S. Rohl.  The array marks[1..marksizn] is global.  The   }
{ procedure WriteArray is global and displays the results.  This must be}
{ evoked with FikePerm(2) in the calling procedure.}
VAR
    dn, dk, temp : INTEGER;
BEGIN
IF 
THEN BEGIN { swap the pair }
    WriteArray;
    temp :=marks[marksize];
    FOR dn :=  DOWNTO 1
    DO BEGIN
        marks[marksize] := marks[dn];
        marks [dn] := temp;
        WriteArray;
        marks[dn] := marks[marksize]
        END;
    marks[marksize] := temp;
    END {of bottom level sequence }
ELSE BEGIN
    FikePerm;
    temp := marks[k];
    FOR dk :=  DOWNTO 1
    DO BEGIN
        marks[k] := marks[dk];
        marks[dk][ := temp;
        FikePerm;
        marks[dk] := marks[k];
        END; { of loop on dk }
    marks[k] := temp;l
    END { of sequence for other levels }
END; { of FikePerm procedure }

BEGIN { Main }
FOR ii := 1 TO marksize
DO marks[ii] := ii;
permcount := 0;
WriteLn ;
WrieLn;
FikePerm ; { It always starts with 2 }
WriteLn ;
ReadLn;
END.

2.


PROGRAM TestLexPerms;
CONST marksize = 5;
VAR
    marks : ARRAY [1..marksize] OF INTEGER;
    ii : INTEGER;
    permcount : INTEGER;

PROCEDURE WriteArray; VAR i : INTEGER; BEGIN FOR i := 1 TO marksize DO Write ; permcount := permcount + 1; WriteLn; END;

PROCEDURE LexPerm ; { Outputs permutations in lexicographic order. The array marks is global } { and has n or fewer marks. The procedure WriteArray () is global and } { displays the results. } VAR work : INTEGER: mp, hlen, i : INTEGER; BEGIN IF THEN BEGIN { Swap the pair } work := marks[1]; marks[1] := marks[2]; marks[2] := work; WriteArray ; END ELSE BEGIN FOR mp := DOWNTO 1 DO BEGIN LexPerm<>; hlen := DIV 2; FOR i := 1 TO hlen DO BEGIN { Another swap } work := marks[i]; marks[i] := marks[n - i]; marks[n - i] := work END; work := marks[n]; { More swapping } marks[n[ := marks[mp]; marks[mp] := work; WriteArray; END; LexPerm<> END; END;

BEGIN { Main } FOR ii := 1 TO marksize DO marks[ii] := ii; permcount := 1; { The starting position is permutation } WriteLn < Starting position: >; WriteLn LexPerm ; WriteLn < PermCount is , permcount>; ReadLn; END.

3.


PROGRAM TestAllPerms;
CONST marksize = 5;
VAR
    marks : ARRAY [1..marksize] of INTEGER;
    ii : INTEGER;
    permcount : INTEGER;

PROCEDURE WriteArray; VAR i : INTEGER; BEGIN FOR i := 1 TO marksize DO Write ; WriteLn; permcount := permcount + 1; END;

PROCEDURE AllPerm (n : INTEGER); { Outputs permutations in nonlexicographic order. The array marks is } { global and has n or few marks. The procedure WriteArray is global and } { displays the results. } VAR work : INTEGER; mp, swaptemp : INTEGER; BEGIN IF THEN BEGIN { Swap the pair } work := marks[1]; marks[1] := marks[2]; marks[2] := work; WriteArray; END ELSE BEGIN FOR mp := DOWNTO 1 DO BEGIN ALLPerm<< n - 1>>; IF > THEN swaptemp := 1 ELSE swaptemp := mp; work := marks[n]; marks[n] := marks[swaptemp}; marks[swaptemp} := work; WriteArray; AllPerm< n-1 >; END; END;

BEGIN { Main } FOR ii := 1 TO marksize DO marks[ii] := ii permcount :=1; WriteLn < Starting position; >; WriteLn; Allperm < marksize>; WriteLn < Perm count is , permcount>; ReadLn; END.

Ответ 5

функция перестановок в clojure.contrib.lazy_seqs уже заявляет, что делает именно это.