This one is a lot simpler than the unrestricted tree generator.
It's interesting to observe that for k
elements, there are exactly (k-1)!
possible general heaps. (If we were generating forests of heaps, there would be k!
possible forests, which is equivalent to generating a single heap with a new node as root.)
The key insight is that the heap property guarantees that the largest element of any subtree is the root of that subtree (and consequently the largest element is the root of the tree). Since we don't care about the order of children, we can agree to place the children in descending order at each node, which will guarantee that the second-largest element in a subtree will be exactly the leftmost child of the root of that subtree.
So we can just place the elements in decreasing order, iterating over all the possible placements. After we make the largest element the root of the tree, each subsequent element in turn can be made the last (or only) child of any previously placed element. (All previously placed children are larger than the new element, so putting it at the first position maintains the canonical child order. And of course, since all previously placed elements are larger, the new element can be a child of any of them.)
With that procedure, at the step where i
elements have already been placed, there are exactly i
possible placements for the next element. Hence the formula (k-1)!
.
Implementing the above is quite straight-forward, although it is anything but a functional solution: the candidate tree is modified at every step. (That means that you would need to do a complete copy of a yielded tree if you intended to modify it or keep it for future reference.)
# 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()
Here's the six heaps built from 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)))
Or, diagrammatically (done by hand)
(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
The fact that the number of heaps is the factorial of the number of non-root nodes suggests that there is an isomorphism between heaps and permutations. And indeed there is, as can be seen with the help of the diagrams above.
We can turn a heap into a permutation by doing a post-order depth-first traverse of the tree. The post-order traverse guarantees that the last node in the walk will be the root.
To go the other way, from a permutation ending with the root label to a heap, we initialise an empty stack and scan the permutation left-to-right. We push each label onto the stack, after first populating its child list by popping any smaller elements from the top of the stack. If the permutation ends with the largest element, it will be the only element on the stack at the end of the scan. (If we allowed arbitrary permutations, we would get the n!
heap forests instead of the (n-1)!
rooted heaps.)
This suggests that we could enumerate heaps by using any convenient method of enumerating permutations and constructing the heap from the permutation:
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:]))