-3

最大堆可以在同一棵树本身内具有任意分支因子。

例如给定 [8,5,3,1] 一些生成的堆可能是

  8      or       8       or    8        
 /|\              |             |
5 1 3             5             5           and so on.....
 (a)             / \            |
                3   1           3
                 (b)            |
                                1
                               (c)

出于我的目的,我认为上面的树 (a) 与下面的树 (d) 和 (e) 相同

  8              8
 /|\            /|\              and so on.....
3 5 1          1 5 3 
 (d)            (e)

编辑 :

(1) 我尝试了一种算法来生成所有可能的树,然后根据 max-heap 属性进行过滤。但显然这是指数级的,因此在 Python 中,即使是超过 10 个元素的列表也需要一段时间。

(2) 因此,我只想构建那些服从最大堆属性而不是过滤但无法从中制定递归子问题的树。

4

1 回答 1

2

这个比无限制的树生成器简单得多。

有趣的是,对于k元素,确实存在(k-1)!可能的一般堆。(如果我们生成堆的森林,就会k!有森林,这相当于生成一个以新节点为根的单个堆。)

关键的见解是堆属性保证任何子树的最大元素是该子树的根(因此最大的元素是树的根)。由于我们不关心子节点的顺序,我们可以同意将子节点按降序排列在每个节点上,这将保证子树中的第二大元素恰好是该子树根的最左边的子节点.

所以我们可以按降序排列元素,遍历所有可能的位置。在我们将最大元素作为树的根之后,每个后续元素依次可以成为任何先前放置的元素的最后(或唯一)子元素。(所有先前放置的子元素都大于新元素,因此将其放在第一个位置可以保持规范的子元素顺序。当然,由于所有先前放置的元素都较大,因此新元素可以是其中任何一个的子元素。)

使用该过程,在i已经放置元素的步骤中i,下一个元素完全有可能放置。因此公式(k-1)!

实现上述内容非常简单,尽管它不是一个功能性解决方案:候选树在每一步都被修改。(这意味着如果您打算修改它或保留它以供将来参考,您将需要对生成的树进行完整的复制。)

# This differs from the tree object in the other answer because
# in this tree, the kids are modified and must therefore be lists
class tree(object):
    def __init__(self, root, kids=()):
        self.root = root
        self.kids = list(kids)
    def __str__(self):
        if self.kids:
            return "(%s %s)" % (self.root,
                                ' '.join(str(kid) for kid in self.kids))
        else:
            return self.root

# The main function turns each label into a singleton (leaf) node, and
# then calls the helper function to recursively stitch them together into
# heaps
def allheaps(labels):
    if labels:
        yield from genheaps(list(map(tree, labels)), 1)

def genheaps(nodes, i):
    if i == len(nodes): yield nodes[0]
    else:
        # For each possible position for node i:
        # Place it, recurse the rest of the nodes, restore the parent.
        for j in range(i):
            nodes[j].kids.append(nodes[i])
            yield from genheaps(nodes, i+1)
            nodes[j].kids.pop()

这是从 8、5、3、1 构建的六个堆:

>>> print('\n'.join(map(str,allheaps("8531"))))
(8 5 3 1)
(8 (5 1) 3)
(8 5 (3 1))
(8 (5 3) 1)
(8 (5 3 1))
(8 (5 (3 1)))

或者,以图解方式(手工完成)

(8 5 3 1) (8 (5 1) 3) (8 5 (3 1)) (8 (5 3) 1) (8 (5 3 1)) (8 (5 (3 1)))
    8          8           8           8           8            8
  / | \      /   \       /   \       /   \         |            |
 5  3  1    5     3     5     3     5      1       5            5
            |                 |     |            /   \          |
            1                 1     3           3     1         3
                                                                |
                                                                1

堆的数量是非根节点数量的阶乘这一事实表明堆和排列之间存在同构。确实有,从上面的图表可以看出。

我们可以通过对树进行后序深度优先遍历来将堆转换为排列。后序遍历保证遍历中的最后一个节点将是根。

换一种方式,从以根标签结尾的排列到堆,我们初始化一个空堆栈并从左到右扫描排列。在首先通过从堆栈顶部弹出任何较小的元素来填充其子列表之后,我们将每个标签推入堆栈。如果排列以最大元素结束,则它将是扫描结束时堆栈上的唯一元素。(如果我们允许任意排列,我们将得到n!堆森林而不是(n-1)!根堆。)

这表明我们可以通过使用任何方便的枚举排列并从排列构造堆的方法来枚举堆:

from itertools import permutations
from functools import reduce
def putlabel(stk, label):
    kids=[]
    while stk and stk[-1].root < label: kids.append(stk.pop())
    stk.append(tree(label, kids))
    return stk

def permtoheap(root, perm):
    '''Construct a heap with root 'root' using the non-root nodes from
       a postorder walk. 'root' should be larger than max(perm); otherwise,
       the result will not satisfy the heap property.
    '''
    return tree(root, reduce(putlabel, perm, []))

def allheaps2(labels):
    if labels:
        yield from (permtoheap(labels[0], p) for p in permutations(labels[1:]))
于 2018-09-05T03:37:57.643 回答