我需要迭代一组整数的排列。必须通过在每一步交换一对元素来生成订单。
我找到了Heap算法的维基百科文章(http://en.wikipedia.org/wiki/Heap%27s_algorithm),这应该是这样做的。伪代码是:
procedure generate(n : integer, A : array of any):
if n = 1 then
output(A)
else
for i := 1; i ≤ n; i += 1 do
generate(n - 1, A)
if n is odd then
j ← 1
else
j ← i
swap(A[j], A[n])
我试图在python中为此编写一个生成器:
def heap_perm(A):
n = len(A)
Alist = [el for el in A]
for hp in _heap_perm_(n, Alist):
yield hp
def _heap_perm_(n, A):
if n == 1:
yield A
else:
for i in range(1,n+1):
for hp in _heap_perm_(n-1, A):
yield hp
if (n % 2) == 1:
j = 1
else:
j = i
swap = A[j-1]
A[j-1] = A[n-1]
A[n-1] = swap
这会按以下顺序产生排列(输入[0,1,2,3]):
0,1,2,3
1,0,2,3
2,0,1,3
0,2,1,3
1,2,0,3
2,1,0,3
3,1,2,0
and so on
直到最后一步,这似乎没有问题,这不是一对交换。
我做错了什么?
答案 0 :(得分:27)
Wikipedia article on Heap's algorithm已经更正,因为这个答案已经写好了,但您可以在Wikipedia's change history中看到问答所引用的版本。
如果您打算实施Wikipedia伪代码,那么您的代码(算法上)没有任何问题。您已成功实现了所提出的算法。
然而,所提出的算法不是Heap的算法,并且它不保证连续的排列将是单个交换的结果。正如您在维基百科页面中看到的,有时在生成的排列之间发生多次交换。参见例如行
CBAD
DBCA
它们之间有三次交换(其中一个交换是无操作)。
这正是代码的输出,这并不奇怪,因为您的代码是所提算法的准确实现。
有趣的是,伪代码基本上来自Sedgewick的谈话幻灯片(维基百科页面上的参考文献3),这与前一张幻灯片上的排列列表不对应。我做了一些调查,发现了这个不正确的伪代码的许多副本,足以开始怀疑我的分析。
幸运的是,我还查看了Heap的简短论文(维基百科页面上的参考文献1),这是相当清楚的。他说的是:(强调补充)
程序使用相同的通用方法...即对于n个对象,首先置换第一个(n-1)个对象,然后将第n个单元格的内容与指定单元格的内容进行交换。在此方法中,如果n为奇数,则此指定单元格始终是第一个单元格,但如果n为偶数,则从1到(n-1)连续编号。
问题是所呈现的递归函数在返回之前总是进行交换(除非N为1)。因此它实际上会按照Heap指定从1连续切换到 n ,而不是(n-1)。因此,当(例如)用N == 3调用函数时,在下一个yield之前调用结束时将有两个交换:一个来自N == 2调用的结尾,另一个来自i == N的循环。如果N == 4,则会有三次交换,依此类推。 (但其中一些将是无操作。)
最后一次交换不正确:算法应该在递归调用之间进行交换,而不是在它们之后;它永远不应与i==N
交换。
所以这应该有效:
def _heap_perm_(n, A):
if n == 1: yield A
else:
for i in range(n-1):
for hp in _heap_perm_(n-1, A): yield hp
j = 0 if (n % 2) == 1 else i
A[j],A[n-1] = A[n-1],A[j]
for hp in _heap_perm_(n-1, A): yield hp
我找到了Sedgewick 1977年的一篇论文,(维基百科给出的链接很糟糕,很遗憾),并且很高兴地发现他在那里提出的算法是正确的。但是,它使用循环控制结构(归功于Donald Knuth),这对Python或C程序员来说可能看起来很陌生:中环测试:
Algorithm 1:
procedure permutations (N);
begin c:=1;
loop:
if N>2 then permutations(N-1)
endif;
while c<N:
# Sedgewick uses :=: as the swap operator
# Also see note below
if N odd then P[N]:=:P[1] else P[N]:=:P[c] endif;
c:=c+l
repeat
end;
注意:交换行取自第141页,其中Sedgewick解释了如何修改算法1的原始版本(第140页)以匹配Heap的算法。除了该行,算法的其余部分保持不变。提出了几种变体。
答案 1 :(得分:-1)
获取列表排列的最简单方法是在itertools模块中使用排列函数。所以,如果算法没有绑定,我会这样做:
from itertools import permutations
a = [1,2,3,4]
for item in permutations(a):
print item
答案 2 :(得分:-2)
只想分享此链接以获取正确的伪代码: http://desple.com/post/118425815657/permutations-with-heaps-algorithm-in-javascript
它与维基百科页面中的伪代码不同:
generate (n, arr)
if n = 1
output arr
else
for i = 0; i < n; i += 1
generate (n - 1, arr)
if n is even
swap(arr[i], arr[n - 1])
else
swap(arr[0], arr[n - 1])
维基百科页面在交换后有一个额外的生成(n-1,arr)语句,这会导致多余的结果。