Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit f31a812

Browse files
KirilBangachevcclauss
authored andcommittedSep 5, 2019
Add Binomial Heap (TheAlgorithms#1146)
* Binomial Heap Implementation of Binomial Heap. Reference: Advanced Data Structures, Peter Brass * Update binomial_heap.py * Update binomial_heap.py * Update binomial_heap.py - Fuller documentation of binomial heap - Update unit tests - Replace printing method by overwriting __str__() * Update binomial_heap.py - Added more tests - Added to the documentation - Stylistic editing - mergeHeaps now also returns a reference to the merged heap - added a preOrder function that returns a list with the preorder of the heap * Update binomial_heap.py Changed the unit tests structure * Turned the tests into doctests
1 parent a4ed40b commit f31a812

File tree

1 file changed

+442
-0
lines changed

1 file changed

+442
-0
lines changed
 

‎data_structures/heap/binomial_heap.py

Lines changed: 442 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,442 @@
1+
"""
2+
Binomial Heap
3+
4+
Reference: Advanced Data Structures, Peter Brass
5+
"""
6+
7+
8+
class Node:
9+
"""
10+
Node in a doubly-linked binomial tree, containing:
11+
- value
12+
- size of left subtree
13+
- link to left, right and parent nodes
14+
"""
15+
16+
def __init__(self, val):
17+
self.val = val
18+
# Number of nodes in left subtree
19+
self.left_tree_size = 0
20+
self.left = None
21+
self.right = None
22+
self.parent = None
23+
24+
def mergeTrees(self, other):
25+
"""
26+
In-place merge of two binomial trees of equal size.
27+
Returns the root of the resulting tree
28+
"""
29+
assert (
30+
self.left_tree_size == other.left_tree_size
31+
), "Unequal Sizes of Blocks"
32+
33+
if self.val < other.val:
34+
other.left = self.right
35+
other.parent = None
36+
if self.right:
37+
self.right.parent = other
38+
self.right = other
39+
self.left_tree_size = (
40+
self.left_tree_size * 2 + 1
41+
)
42+
return self
43+
else:
44+
self.left = other.right
45+
self.parent = None
46+
if other.right:
47+
other.right.parent = self
48+
other.right = self
49+
other.left_tree_size = (
50+
other.left_tree_size * 2 + 1
51+
)
52+
return other
53+
54+
55+
class BinomialHeap:
56+
"""
57+
Min-oriented priority queue implemented with the Binomial Heap data
58+
structure implemented with the BinomialHeap class. It supports:
59+
60+
- Insert element in a heap with n elemnts: Guaranteed logn, amoratized 1
61+
- Merge (meld) heaps of size m and n: O(logn + logm)
62+
- Delete Min: O(logn)
63+
- Peek (return min without deleting it): O(1)
64+
65+
Example:
66+
67+
Create a random permutation of 30 integers to be inserted and
68+
19 of them deleted
69+
>>> import numpy as np
70+
>>> permutation = np.random.permutation(list(range(30)))
71+
72+
Create a Heap and insert the 30 integers
73+
74+
__init__() test
75+
>>> first_heap = BinomialHeap()
76+
77+
30 inserts - insert() test
78+
>>> for number in permutation:
79+
... first_heap.insert(number)
80+
81+
Size test
82+
>>> print(first_heap.size)
83+
30
84+
85+
Deleting - delete() test
86+
>>> for i in range(25):
87+
... print(first_heap.deleteMin(), end=" ")
88+
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
89+
90+
Create a new Heap
91+
>>> second_heap = BinomialHeap()
92+
>>> vals = [17, 20, 31, 34]
93+
>>> for value in vals:
94+
... second_heap.insert(value)
95+
96+
97+
The heap should have the following structure:
98+
99+
17
100+
/ \
101+
# 31
102+
/ \
103+
20 34
104+
/ \ / \
105+
# # # #
106+
107+
preOrder() test
108+
>>> print(second_heap.preOrder())
109+
[(17, 0), ('#', 1), (31, 1), (20, 2), ('#', 3), ('#', 3), (34, 2), ('#', 3), ('#', 3)]
110+
111+
printing Heap - __str__() test
112+
>>> print(second_heap)
113+
17
114+
-#
115+
-31
116+
--20
117+
---#
118+
---#
119+
--34
120+
---#
121+
---#
122+
123+
mergeHeaps() test
124+
>>> merged = second_heap.mergeHeaps(first_heap)
125+
>>> merged.peek()
126+
17
127+
128+
values in merged heap; (merge is inplace)
129+
>>> while not first_heap.isEmpty():
130+
... print(first_heap.deleteMin(), end=" ")
131+
17 20 25 26 27 28 29 31 34
132+
133+
"""
134+
135+
def __init__(
136+
self, bottom_root=None, min_node=None, heap_size=0
137+
):
138+
self.size = heap_size
139+
self.bottom_root = bottom_root
140+
self.min_node = min_node
141+
142+
def mergeHeaps(self, other):
143+
"""
144+
In-place merge of two binomial heaps.
145+
Both of them become the resulting merged heap
146+
"""
147+
148+
# Empty heaps corner cases
149+
if other.size == 0:
150+
return
151+
if self.size == 0:
152+
self.size = other.size
153+
self.bottom_root = other.bottom_root
154+
self.min_node = other.min_node
155+
return
156+
# Update size
157+
self.size = self.size + other.size
158+
159+
# Update min.node
160+
if self.min_node.val > other.min_node.val:
161+
self.min_node = other.min_node
162+
# Merge
163+
164+
# Order roots by left_subtree_size
165+
combined_roots_list = []
166+
i, j = self.bottom_root, other.bottom_root
167+
while i or j:
168+
if i and (
169+
(not j)
170+
or i.left_tree_size < j.left_tree_size
171+
):
172+
combined_roots_list.append((i, True))
173+
i = i.parent
174+
else:
175+
combined_roots_list.append((j, False))
176+
j = j.parent
177+
# Insert links between them
178+
for i in range(len(combined_roots_list) - 1):
179+
if (
180+
combined_roots_list[i][1]
181+
!= combined_roots_list[i + 1][1]
182+
):
183+
combined_roots_list[i][
184+
0
185+
].parent = combined_roots_list[i + 1][0]
186+
combined_roots_list[i + 1][
187+
0
188+
].left = combined_roots_list[i][0]
189+
# Consecutively merge roots with same left_tree_size
190+
i = combined_roots_list[0][0]
191+
while i.parent:
192+
if (
193+
(
194+
i.left_tree_size
195+
== i.parent.left_tree_size
196+
)
197+
and (not i.parent.parent)
198+
) or (
199+
i.left_tree_size == i.parent.left_tree_size
200+
and i.left_tree_size
201+
!= i.parent.parent.left_tree_size
202+
):
203+
204+
# Neighbouring Nodes
205+
previous_node = i.left
206+
next_node = i.parent.parent
207+
208+
# Merging trees
209+
i = i.mergeTrees(i.parent)
210+
211+
# Updating links
212+
i.left = previous_node
213+
i.parent = next_node
214+
if previous_node:
215+
previous_node.parent = i
216+
if next_node:
217+
next_node.left = i
218+
else:
219+
i = i.parent
220+
# Updating self.bottom_root
221+
while i.left:
222+
i = i.left
223+
self.bottom_root = i
224+
225+
# Update other
226+
other.size = self.size
227+
other.bottom_root = self.bottom_root
228+
other.min_node = self.min_node
229+
230+
# Return the merged heap
231+
return self
232+
233+
def insert(self, val):
234+
"""
235+
insert a value in the heap
236+
"""
237+
if self.size == 0:
238+
self.bottom_root = Node(val)
239+
self.size = 1
240+
self.min_node = self.bottom_root
241+
else:
242+
# Create new node
243+
new_node = Node(val)
244+
245+
# Update size
246+
self.size += 1
247+
248+
# update min_node
249+
if val < self.min_node.val:
250+
self.min_node = new_node
251+
# Put new_node as a bottom_root in heap
252+
self.bottom_root.left = new_node
253+
new_node.parent = self.bottom_root
254+
self.bottom_root = new_node
255+
256+
# Consecutively merge roots with same left_tree_size
257+
while (
258+
self.bottom_root.parent
259+
and self.bottom_root.left_tree_size
260+
== self.bottom_root.parent.left_tree_size
261+
):
262+
263+
# Next node
264+
next_node = self.bottom_root.parent.parent
265+
266+
# Merge
267+
self.bottom_root = self.bottom_root.mergeTrees(
268+
self.bottom_root.parent
269+
)
270+
271+
# Update Links
272+
self.bottom_root.parent = next_node
273+
self.bottom_root.left = None
274+
if next_node:
275+
next_node.left = self.bottom_root
276+
277+
def peek(self):
278+
"""
279+
return min element without deleting it
280+
"""
281+
return self.min_node.val
282+
283+
def isEmpty(self):
284+
return self.size == 0
285+
286+
def deleteMin(self):
287+
"""
288+
delete min element and return it
289+
"""
290+
# assert not self.isEmpty(), "Empty Heap"
291+
292+
# Save minimal value
293+
min_value = self.min_node.val
294+
295+
# Last element in heap corner case
296+
if self.size == 1:
297+
# Update size
298+
self.size = 0
299+
300+
# Update bottom root
301+
self.bottom_root = None
302+
303+
# Update min_node
304+
self.min_node = None
305+
306+
return min_value
307+
# No right subtree corner case
308+
# The structure of the tree implies that this should be the bottom root
309+
# and there is at least one other root
310+
if self.min_node.right == None:
311+
# Update size
312+
self.size -= 1
313+
314+
# Update bottom root
315+
self.bottom_root = self.bottom_root.parent
316+
self.bottom_root.left = None
317+
318+
# Update min_node
319+
self.min_node = self.bottom_root
320+
i = self.bottom_root.parent
321+
while i:
322+
if i.val < self.min_node.val:
323+
self.min_node = i
324+
i = i.parent
325+
return min_value
326+
# General case
327+
# Find the BinomialHeap of the right subtree of min_node
328+
bottom_of_new = self.min_node.right
329+
bottom_of_new.parent = None
330+
min_of_new = bottom_of_new
331+
size_of_new = 1
332+
333+
# Size, min_node and bottom_root
334+
while bottom_of_new.left:
335+
size_of_new = size_of_new * 2 + 1
336+
bottom_of_new = bottom_of_new.left
337+
if bottom_of_new.val < min_of_new.val:
338+
min_of_new = bottom_of_new
339+
# Corner case of single root on top left path
340+
if (not self.min_node.left) and (
341+
not self.min_node.parent
342+
):
343+
self.size = size_of_new
344+
self.bottom_root = bottom_of_new
345+
self.min_node = min_of_new
346+
# print("Single root, multiple nodes case")
347+
return min_value
348+
# Remaining cases
349+
# Construct heap of right subtree
350+
newHeap = BinomialHeap(
351+
bottom_root=bottom_of_new,
352+
min_node=min_of_new,
353+
heap_size=size_of_new,
354+
)
355+
356+
# Update size
357+
self.size = self.size - 1 - size_of_new
358+
359+
# Neighbour nodes
360+
previous_node = self.min_node.left
361+
next_node = self.min_node.parent
362+
363+
# Initialize new bottom_root and min_node
364+
self.min_node = previous_node or next_node
365+
self.bottom_root = next_node
366+
367+
# Update links of previous_node and search below for new min_node and
368+
# bottom_root
369+
if previous_node:
370+
previous_node.parent = next_node
371+
372+
# Update bottom_root and search for min_node below
373+
self.bottom_root = previous_node
374+
self.min_node = previous_node
375+
while self.bottom_root.left:
376+
self.bottom_root = self.bottom_root.left
377+
if self.bottom_root.val < self.min_node.val:
378+
self.min_node = self.bottom_root
379+
if next_node:
380+
next_node.left = previous_node
381+
382+
# Search for new min_node above min_node
383+
i = next_node
384+
while i:
385+
if i.val < self.min_node.val:
386+
self.min_node = i
387+
i = i.parent
388+
# Merge heaps
389+
self.mergeHeaps(newHeap)
390+
391+
return min_value
392+
393+
def preOrder(self):
394+
"""
395+
Returns the Pre-order representation of the heap including
396+
values of nodes plus their level distance from the root;
397+
Empty nodes appear as #
398+
"""
399+
# Find top root
400+
top_root = self.bottom_root
401+
while top_root.parent:
402+
top_root = top_root.parent
403+
# preorder
404+
heap_preOrder = []
405+
self.__traversal(top_root, heap_preOrder)
406+
return heap_preOrder
407+
408+
def __traversal(self, curr_node, preorder, level=0):
409+
"""
410+
Pre-order traversal of nodes
411+
"""
412+
if curr_node:
413+
preorder.append((curr_node.val, level))
414+
self.__traversal(
415+
curr_node.left, preorder, level + 1
416+
)
417+
self.__traversal(
418+
curr_node.right, preorder, level + 1
419+
)
420+
else:
421+
preorder.append(("#", level))
422+
423+
def __str__(self):
424+
"""
425+
Overwriting str for a pre-order print of nodes in heap;
426+
Performance is poor, so use only for small examples
427+
"""
428+
if self.isEmpty():
429+
return ""
430+
preorder_heap = self.preOrder()
431+
432+
return "\n".join(
433+
("-" * level + str(value))
434+
for value, level in preorder_heap
435+
)
436+
437+
438+
# Unit Tests
439+
if __name__ == "__main__":
440+
import doctest
441+
442+
doctest.testmod()

0 commit comments

Comments
 (0)
Please sign in to comment.