[algorithm] 느리게 순열 생성
Clojure에서 게으른 목록을 만들 수있는 방식으로 집합의 순열을 생성하는 알고리즘을 찾고 있습니다. 즉, 요청할 때까지 각 순열이 계산되지 않는 순열 목록을 반복하고 모든 순열을 한 번에 메모리에 저장할 필요가 없습니다.
또는 특정 집합이 주어지면 해당 집합의 “다음”순열을 반환하는 알고리즘을 찾고 있습니다. 이러한 방식으로 자체 출력에서 함수를 반복적으로 호출하면 원래 집합의 모든 순열이 순환됩니다. 어떤 순서 (순서는 중요하지 않습니다).
그러한 알고리즘이 있습니까? 내가 본 대부분의 순열 생성 알고리즘은 한 번에 (일반적으로 재귀 적으로) 생성하는 경향이 있으며, 이는 매우 큰 집합으로 확장되지 않습니다. Clojure (또는 다른 기능적 언어)로 구현하면 도움이되지만 의사 코드에서 알아낼 수 있습니다.
답변
네, 이다 는 “다음 순열”알고리즘, 그것도 아주 간단합니다. C ++ 표준 템플릿 라이브러리 (STL)에는 next_permutation
.
알고리즘은 실제로 다음 순열, 즉 사전 식으로 다음 순열을 찾습니다 . 아이디어는 다음과 같습니다. “32541”과 같이 시퀀스가 주어 졌다고 가정합니다. 다음 순열은 무엇입니까?
생각해 보면 “34125”라는 것을 알 수 있습니다. 그리고 당신의 생각은 아마도 다음과 같을 것입니다 : “32541”에서,
- “32”를 고정 상태로 유지하고 “541”부분에서 나중에 순열을 찾을 수있는 방법이 없습니다. 해당 순열은 이미 5,4 및 1에 대한 마지막 순열이기 때문에 내림차순으로 정렬됩니다.
- 따라서 “2”를 더 큰 값으로 변경해야합니다. 사실 “541”부분보다 더 큰 숫자, 즉 4로 변경해야합니다.
- 이제 순열이 “34”로 시작하기로 결정했다면 나머지 숫자는 오름차순이되어야하므로 답은 “34125”입니다.
알고리즘은 다음과 같은 추론 라인을 정확하게 구현하는 것입니다.
- 내림차순으로 정렬 된 가장 긴 “꼬리”를 찾으십시오. ( “541”부분)
- 꼬리 바로 앞의 숫자 ( “2”)를 꼬리보다 큰 숫자 (4)로 변경합니다.
- 꼬리를 오름차순으로 정렬합니다.
이전 요소가 현재 요소보다 작지 않은 한 끝에서 시작하여 뒤로 이동하여 (1.) 효율적으로 수행 할 수 있습니다. “4”를 “2”로 바꾸면 (2.)를 할 수 있으므로 “34521”이됩니다. 이렇게하면 (3.)에 대한 정렬 알고리즘을 사용하지 않아도됩니다. 내림차순으로 정렬되어 있고 여전히 (이것에 대해 생각 해보세요), 그래서 그것은 반대로 만하면됩니다.
C ++ 코드는이를 정확하게 수행합니다 ( /usr/include/c++/4.0.0/bits/stl_algo.h
시스템 의 소스를 보거나이 기사를 참조 하십시오 ). 여러분의 언어로 번역하는 것은 간단해야합니다. [C ++ 반복자에 익숙하지 않은 경우 “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”를 제공합니다.
답변
순열되는 값에 대한 사전 식 순서에 대해 이야기하고 있다고 가정하면 다음과 같은 두 가지 일반적인 접근 방식을 사용할 수 있습니다.
- 요소의 하나의 순열을 다음 순열로 변환합니다 (ShreevatsaR 게시 됨).
- 0에서 위로
n
세면서 th 순열을 직접 계산합니다n
.
C ++를 네이티브로 사용하지 않는 사람들 (나와 같은 ;-)의 경우, “왼쪽”에 인덱스 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
th 순열 의 직접 계산)의 경우 요소 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
예를 들어 ABCD의 13 번째 순열은 다음과 같습니다.
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's only one element)
DB
ADB
CADB
부수적으로, 요소의 “제거”는 여전히 사용 가능한 요소를 나타내는 부울의 병렬 배열로 나타낼 수 있으므로 각 재귀 호출에서 새 배열을 만들 필요가 없습니다.
따라서 ABCD의 순열을 반복하려면 0에서 23 (4! -1)까지 세고 해당 순열을 직접 계산합니다.
답변
wikipeda 의 순열 기사 를 확인해야합니다 . 또한 Factoradic number 의 개념이 있습니다.
어쨌든 수학적 문제는 꽤 어렵습니다.
에서 C#
를 사용 iterator
하고을 사용하여 순열 알고리즘을 중지 할 수 있습니다 yield
. 이것의 문제는 당신이 앞뒤로 갈 수 없거나 index
.
답변
이를 생성하기위한 순열 알고리즘의 더 많은 예.
출처 : http://www.ddj.com/architect/201200326
- 가장 빨리 알려진 Fike의 알고리즘을 사용합니다.
- 사전 순서에 Algo를 사용합니다.
- 비 사전을 사용하지만 항목 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.
삼.
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.
답변
clojure.contrib.lazy_seqs의 순열 함수는 이미 이것을 수행한다고 주장합니다.