python - 堆的算法置换生成器

标签 python algorithm permutation

我需要迭代整数元组的排列。必须通过在每一步交换一对元素来生成顺序。

我找到了堆算法的维基百科文章( 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

这似乎很好,直到最后一步,这不是一对交换。

我究竟做错了什么?

最佳答案

历史序幕

Wikipedia article on Heap's algorithm自从撰写此答案以来已更正,但您可以在 Wikipedia's change history 中看到问题和答案中提到的版本.

如果您打算实现维基百科伪代码,那么您的代码(在算法上)没有问题。您已成功实现了所提供的算法。

但是,所提供的算法不是 Heap 的算法,并且它不保证连续排列将是单个交换的结果。正如您在维基百科页面中看到的那样,有时生成的排列之间会发生多次交换。参见例如线条

CBAD
DBCA

它们之间有三个交换(交换之一是空操作)。

这正是您代码的输出,这并不奇怪,因为您的代码是所提供算法的准确实现。

Heap算法的正确实现,以及错误的来源

有趣的是,伪代码基本上来自 Sedgewick 的演讲幻灯片(维基百科页面上的引用文献 3),它与前一张幻灯片上的排列列表不对应。我做了一些调查,发现了很多这个错误伪代码的副本,足以让我开始怀疑我的分析。

幸运的是,我还查看了 Heap 的简短论文(维基百科页面上的引用文献 1),内容相当清晰。他说的是:(加重点)

The program uses the same general method … i.e. for n objects, first permute the first (n—1) objects and then interchange the contents of the nth cell with those of a specified cell. In this method this specified cell is always the first cell if n is odd, but if n is even, it is numbered consecutively from 1 to (n−1).



问题是所呈现的递归函数总是在返回之前进行交换(除非 N 为 1)。所以它实际上从 1 到 连续交换 ,不是 (n−1) 正如堆指定的那样。因此,当(例如)以 N==3 调用该函数时,在下一次 yield 之前的调用结束时将有两次交换:一次来自 N==2 调用的结束,另一次来自i==N 的循环。如果 N==4,则将有 3 次交换,依此类推。 (不过,其中一些将是无操作的。)

最后一次交换不正确:算法应该在递归调用之间进行交换,而不是在它们之后;它永远不应该与 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 页)以匹配堆的算法。除了那条线之外,算法的其余部分没有变化。介绍了几种变体。

关于python - 堆的算法置换生成器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29042819/

相关文章:

C 代码排列

python - 修复 Bokeh 放大和缩小的边界

algorithm - 从数组中选取随机数

algorithm - 计算一个数字的表示次数作为斐波那契数的总和

c++ - 大集合的第 n 个或任意组合

r - 在 R 中生成随机排列

python - 无法在 python 应用程序的 app.yaml 中设置缓存过期

javascript - 动态创建输入,然后使用 Flask 从输入中获取数据

python - 比较两个 csv 文件并添加两个文件中不常见的列

java - 如何有效地计算 Java 中间隔列表中点列表的命中次数?