我正在寻找一种算法来生成集合的排列,这样我就可以在Clojure中创建它们的惰性列表。即我想迭代一个排列列表,其中每个排列都不会被计算,直到我请求它为止,并且所有排列都不必一次存储在内存中。
或者我正在寻找一种给定某个集合的算法,它将返回该集合的“下一个”排列,以这种方式在自己的输出上重复调用该函数将循环遍历原始的所有排列。以某种顺序设定(顺序无关紧要)。
有这样的算法吗?我见过的大多数排列生成算法都倾向于一次性生成它们(通常是递归的),它们不能扩展到非常大的集合。 Clojure(或其他函数式语言)中的实现会很有帮助,但我可以从伪代码中找出它。
答案 0 :(得分:134)
是的,是一个“下一个排列”算法,它也很简单。 C ++标准模板库(STL)甚至有一个名为next_permutation
的函数。
该算法实际上找到 next 排列 - 按字典顺序排列下一个排列。这个想法是这样的:假设给你一个序列,比如“32541”。下一个排列是什么?
如果你考虑一下,你会发现它是“34125”。你的想法可能是这样的:在“32541”中,
算法是精确地实现这一推理:
只要前一个元素不小于当前元素,您就可以通过从结尾开始并向后返回来有效地执行(1.)。您可以通过将“4”与“2”交换来执行(2.),因此您将拥有“34521”。一旦执行此操作,您可以避免使用(3.)的排序算法,因为尾部是,现在仍然(想想这个),按递减顺序排序,所以只需要反转。
C ++代码就是这样做的(查看系统/usr/include/c++/4.0.0/bits/stl_algo.h
中的来源,或查看this article);将它翻译成您的语言应该很简单:[如果您不熟悉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”,这是下一个排列。
答案 1 :(得分:41)
假设我们正在谈论关于被置换的值的词典顺序,您可以使用两种通用方法:
n
排列,同时将n
从0向上计算。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
因此,例如,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)计数并直接计算相应的排列。
答案 2 :(得分:3)
您应该查看wikipeda上的Permutations article。此外,还有Factoradic数字的概念。
无论如何,数学问题非常困难。
在C#
中,您可以使用iterator
,并使用yield
停止排列算法。这样做的问题是你不能来回走动,或使用index
。
答案 3 :(得分:3)
更多用于生成它们的置换算法的例子。
来源:http://www.ddj.com/architect/201200326
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.
答案 4 :(得分:2)
clojure.contrib.lazy_seqs中的排列函数已声称要做到这一点。