当前位置: 首页 > 面试题库 >

堆的算法排列生成器

司寇高洁
2023-03-14
问题内容

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

我找到了有关Heap算法的Wikipedia文章(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

在最后一步之前,这似乎还不错,这不是一对交换。

我究竟做错了什么?


问题答案:

历史序幕

自编写此答案以来,
有关Heap算法
的Wikipedia文章已得到纠正,但是您可以在Wikipedia的更改历史记录中看到该问题和答案所引用的版本。

如果打算实现Wikipedia伪代码,则代码(从算法上)没有任何问题。您已经成功实现了所介绍的算法。

但是,提出的算法不是Heap的算法,并且不能保证连续的置换将是单个交换的结果。如您在Wikipedia页面上所见,有时在生成的排列之间会发生多次交换。参见例如线条

CBAD
DBCA

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

这恰好是代码的输出,这并不奇怪,因为您的代码是所呈现算法的准确实现。

堆算法的正确实现以及错误的根源

有趣的是,伪代码基本上是从Sedgewick的演讲幻灯片(Wikipedia页面上的参考文献3)派生而来的,该幻灯片与前一张幻灯片中的排列列表不对应。我进行了一些摸索,发现了许多不正确的伪代码副本,足以使我怀疑我的分析。

幸运的是,我还查看了Heap的简短论文(Wikipedia页面上的参考文献1),该论文相当清楚。他说的是:(强调添加)

该程序使用相同的通用方法…即,对于n个对象,首先置换第一个(n-1)个对象,然后将第n个单元格的内容与指定单元格的内容互换。在此方法中,如果n为奇数,则此指定的单元始终是第一个单元,但如果n为偶,则从1到
(n-1) 连续编号。

问题在于所提供的递归函数总是在返回之前进行交换(除非N为1)。因此,实际上它确实从1连续交换到 n ,而不是Heap指定的 (n-1)
交换。因此,当(例如)以N == 3调用该函数时,在调用结束时将有两次交换在下一次收益率之前:一次是在N == 2调用结束时进行,另一次是在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: 输入:堆栈:3 2 1输出:3 2 1示例2: 输入:堆栈:11 2 32 3 41输出:41 32 11 3 2 您的任务: 预期时间复杂度:O(N*N)预期辅助空间:O(N)递归。 约束:1

  • 示例数据 # heapq_heapdata.py # This data was generated with the random module. data = [19, 9, 4, 10, 11] # heapq_showtree.py import math from io import StringIO def show_tree(tree, total_width=36, fil

  • 本文向大家介绍C#排序算法之堆排序,包括了C#排序算法之堆排序的使用技巧和注意事项,需要的朋友参考一下 本文实例为大家分享了C#实现堆排序的具体代码,供大家参考,具体内容如下 代码: 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持呐喊教程。

  • 本文向大家介绍JAVA堆排序算法的讲解,包括了JAVA堆排序算法的讲解的使用技巧和注意事项,需要的朋友参考一下 预备知识 堆排序   堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。首先简单了解下堆结构。 堆   堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个

  • 哪种排序算法适合对堆栈进行排序以提高空间效率?我需要对堆栈进行“就地”排序。此外,我对“就地”算法的理解是它们不使用任何其他数据结构 - 这是正确的吗? 我知道这与这个问题相似,但我想知道堆栈是否会有所不同?我知道堆栈可以只是一种链接列表,但是你只能访问顶部的事实会改变你的做法吗?

  • 本文向大家介绍Javascript堆排序算法详解,包括了Javascript堆排序算法详解的使用技巧和注意事项,需要的朋友参考一下 堆排序分为两个过程: 1.建堆。 堆实质上是完全二叉树,必须满足:树中任一非叶子结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。 堆分为:大根堆和小根堆,升序排序采用大根堆,降序排序采用小根堆。 如果是大根堆,则通过调整函数将值最大的节点调整至堆根。

  • 本文向大家介绍C++堆排序算法的实现方法,包括了C++堆排序算法的实现方法的使用技巧和注意事项,需要的朋友参考一下  本文实例讲述了C++实现堆排序算法的方法,相信对于大家学习数据结构与算法会起到一定的帮助作用。具体内容如下:  首先,由于堆排序算法说起来比较长,所以在这里单独讲一下。堆排序是一种树形选择排序方法,它的特点是:在排序过程中,将L[n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉

  • 以下是我的想法: 对于列表中的每个元素,我可以删除它,然后要求递归函数返回长度的所有排列,然后将删除的数字添加到每个排列中。然后对列表中的所有数字重复此过程。 基本情况是列表为空或只包含一个元素。在这些情况下,我只返回列表。也就是说,只要k小于或等于列表的长度(例如,如果是,但是,我就不能产生任何长度排列)。 我应该如何修改这个?