如何从数字列表中生成所有可能的最大堆?

时间:2018-09-04 20:02:11

标签: python algorithm tree heap max-heap

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

对于给定的[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)因此,我只想构造那些服从max-heap属性而不是过滤但无法从中提出递归子问题的树。

1 个答案:

答案 0 :(得分:2)

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

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

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

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

通过该过程,在已经放置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:]))