Skip to content

Commit b2e3900

Browse files
authoredDec 30, 2019
[WIP] Added Binomial Heap (#68)
* added BinomialTreeNode * added BinomialTree * added BinomialHeap
·
v0.0.1v0.0.1-alpha
1 parent d754853 commit b2e3900

File tree

8 files changed

+485
-15
lines changed

8 files changed

+485
-15
lines changed
 

‎pydatastructs/miscellaneous_data_structures/__init__.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22

33
from . import (
44
stack,
5+
binomial_trees
56
)
67

8+
from .binomial_trees import (
9+
BinomialTree
10+
)
11+
__all__.extend(binomial_trees.__all__)
12+
713
from .stack import (
814
Stack,
915
)
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
from pydatastructs.utils.misc_util import BinomialTreeNode, _check_type
2+
3+
__all__ = [
4+
'BinomialTree'
5+
]
6+
7+
class BinomialTree(object):
8+
"""
9+
Represents binomial trees
10+
11+
Parameters
12+
==========
13+
14+
root: BinomialTreeNode
15+
The root of the binomial tree.
16+
By default, None
17+
order: int
18+
The order of the binomial tree.
19+
By default, None
20+
21+
Examples
22+
========
23+
24+
>>> from pydatastructs import BinomialTree, BinomialTreeNode
25+
>>> root = BinomialTreeNode(1, 1)
26+
>>> tree = BinomialTree(root, 0)
27+
>>> tree.is_empty
28+
False
29+
30+
References
31+
==========
32+
33+
.. [1] https://en.wikipedia.org/wiki/Binomial_heap
34+
"""
35+
__slots__ = ['root', 'order']
36+
37+
def __new__(cls, root=None, order=None):
38+
if root is not None and \
39+
not _check_type(root, BinomialTreeNode):
40+
raise TypeError("%s i.e., root should be of "
41+
"type BinomialTreeNode."%(root))
42+
if order is not None and not _check_type(order, int):
43+
raise TypeError("%s i.e., order should be of "
44+
"type int."%(order))
45+
obj = object.__new__(cls)
46+
if root is not None:
47+
root.is_root = True
48+
obj.root = root
49+
obj.order = order
50+
return obj
51+
52+
def add_sub_tree(self, other_tree):
53+
"""
54+
Adds a sub tree to current tree.
55+
56+
Parameters
57+
==========
58+
59+
other_tree: BinomialTree
60+
61+
Raises
62+
======
63+
64+
ValueError: If order of the two trees
65+
are different.
66+
"""
67+
if not _check_type(other_tree, BinomialTree):
68+
raise TypeError("%s i.e., other_tree should be of "
69+
"type BinomialTree"%(other_tree))
70+
if self.order != other_tree.order:
71+
raise ValueError("Orders of both the trees should be same.")
72+
self.root.children.append(other_tree.root)
73+
other_tree.root.parent = self.root
74+
other_tree.root.is_root = False
75+
self.order += 1
76+
77+
@property
78+
def is_empty(self):
79+
return self.root is None
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from pydatastructs.miscellaneous_data_structures.binomial_trees import BinomialTree
2+
from pydatastructs.utils.raises_util import raises
3+
from pydatastructs.utils.misc_util import BinomialTreeNode
4+
5+
# only tests the corner cases
6+
def test_BinomialTree():
7+
assert raises(TypeError, lambda: BinomialTree(1, 1))
8+
assert raises(TypeError, lambda: BinomialTree(None, 1.5))
9+
10+
bt = BinomialTree()
11+
assert raises(TypeError, lambda: bt.add_sub_tree(None))
12+
bt1 = BinomialTree(BinomialTreeNode(1, 1), 0)
13+
node = BinomialTreeNode(2, 2)
14+
node.add_children(BinomialTreeNode(3, 3))
15+
bt2 = BinomialTree(node, 1)
16+
assert raises(ValueError, lambda: bt1.add_sub_tree(bt2))
17+
assert bt1.is_empty is False

‎pydatastructs/trees/__init__.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
__all__.extend(space_partitioning_trees.__all__)
1717

1818
from .heaps import (
19-
BinaryHeap
19+
BinaryHeap,
20+
BinomialHeap
2021
)
2122
__all__.extend(heaps.__all__)

‎pydatastructs/trees/heaps.py‎

Lines changed: 221 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
from pydatastructs.utils.misc_util import _check_type, NoneType, TreeNode
2-
from pydatastructs.linear_data_structures.arrays import ArrayForTrees
1+
from pydatastructs.utils.misc_util import _check_type, NoneType, TreeNode, BinomialTreeNode
2+
from pydatastructs.linear_data_structures.arrays import (ArrayForTrees,
3+
DynamicOneDimensionalArray)
4+
from pydatastructs.miscellaneous_data_structures.binomial_trees import BinomialTree
35

46
__all__ = [
5-
'BinaryHeap'
7+
'BinaryHeap',
8+
'BinomialHeap'
69
]
710

8-
class Heap:
11+
class Heap(object):
912
"""
1013
Abstract class for representing heaps.
1114
"""
@@ -181,3 +184,217 @@ def __str__(self):
181184
node.key, node.data,
182185
node.right if node.right <= self._last_pos_filled else None)
183186
return str(to_be_printed)
187+
188+
189+
class BinomialHeap(Heap):
190+
"""
191+
Represents binomial heap.
192+
193+
Parameters
194+
==========
195+
196+
root_list: list/tuple
197+
By default, []
198+
The list of BinomialTree object references
199+
in sorted order.
200+
201+
Examples
202+
========
203+
204+
>>> from pydatastructs import BinomialHeap
205+
>>> b = BinomialHeap()
206+
>>> b.insert(1, 1)
207+
>>> b.insert(2, 2)
208+
>>> b.find_minimum().key
209+
1
210+
>>> b.find_minimum().children[0].key
211+
2
212+
213+
References
214+
==========
215+
216+
.. [1] https://en.wikipedia.org/wiki/Binomial_heap
217+
"""
218+
__slots__ = ['root_list']
219+
220+
def __new__(cls, root_list=[]):
221+
if not all((_check_type(root, BinomialTree))
222+
for root in root_list):
223+
raise TypeError("The root_list should contain "
224+
"references to objects of BinomialTree.")
225+
obj = Heap.__new__(cls)
226+
obj.root_list = DynamicOneDimensionalArray(BinomialTree, root_list)
227+
return obj
228+
229+
def merge_tree(self, tree1, tree2):
230+
"""
231+
Merges two BinomialTree objects.
232+
233+
Parameters
234+
==========
235+
236+
tree1: BinomialTree
237+
238+
tree2: BinomialTree
239+
"""
240+
if (not _check_type(tree1, BinomialTree)) or \
241+
(not _check_type(tree2, BinomialTree)):
242+
raise TypeError("Both the trees should be of type "
243+
"BinomalTree.")
244+
ret_value = None
245+
if tree1.root.key <= tree2.root.key:
246+
tree1.add_sub_tree(tree2)
247+
ret_value = tree1
248+
else:
249+
tree2.add_sub_tree(tree1)
250+
ret_value = tree2
251+
return ret_value
252+
253+
def _merge_heap_last_new_tree(self, new_root_list, new_tree):
254+
"""
255+
Merges last tree node in root list with the incoming tree.
256+
"""
257+
pos = new_root_list._last_pos_filled
258+
if (new_root_list.size != 0) and new_root_list[pos].order == new_tree.order:
259+
new_root_list[pos] = self.merge_tree(new_root_list[pos], new_tree)
260+
else:
261+
new_root_list.append(new_tree)
262+
263+
def merge(self, other_heap):
264+
"""
265+
Merges current binomial heap with the given binomial heap.
266+
267+
Parameters
268+
==========
269+
270+
other_heap: BinomialHeap
271+
"""
272+
if not _check_type(other_heap, BinomialHeap):
273+
raise TypeError("Other heap is not of type BinomialHeap.")
274+
new_root_list = DynamicOneDimensionalArray(BinomialTree, 0)
275+
i, j = 0, 0
276+
while ((i <= self.root_list._last_pos_filled) and
277+
(j <= other_heap.root_list._last_pos_filled)):
278+
new_tree = None
279+
while self.root_list[i] is None:
280+
i += 1
281+
while other_heap.root_list[j] is None:
282+
j += 1
283+
if self.root_list[i].order == other_heap.root_list[j].order:
284+
new_tree = self.merge_tree(self.root_list[i],
285+
other_heap.root_list[j])
286+
i += 1
287+
j += 1
288+
else:
289+
if self.root_list[i].order < other_heap.root_list[j].order:
290+
new_tree = self.root_list[i]
291+
i += 1
292+
else:
293+
new_tree = other_heap.root_list[j]
294+
j += 1
295+
self._merge_heap_last_new_tree(new_root_list, new_tree)
296+
297+
while i <= self.root_list._last_pos_filled:
298+
new_tree = self.root_list[i]
299+
self._merge_heap_last_new_tree(new_root_list, new_tree)
300+
i += 1
301+
while j <= other_heap.root_list._last_pos_filled:
302+
new_tree = other_heap.root_list[j]
303+
self._merge_heap_last_new_tree(new_root_list, new_tree)
304+
j += 1
305+
self.root_list = new_root_list
306+
307+
def insert(self, key, data):
308+
"""
309+
Inserts new node with the given key and data.
310+
311+
key
312+
The key of the node which can be operated
313+
upon by relational operators.
314+
315+
data
316+
The data to be stored in the new node.
317+
"""
318+
new_node = BinomialTreeNode(key, data)
319+
new_tree = BinomialTree(root=new_node, order=0)
320+
new_heap = BinomialHeap(root_list=[new_tree])
321+
self.merge(new_heap)
322+
323+
def find_minimum(self, **kwargs):
324+
"""
325+
Finds the node with the minimum key.
326+
327+
Returns
328+
=======
329+
330+
min_node: BinomialTreeNode
331+
"""
332+
if self.is_empty:
333+
raise ValueError("Binomial heap is empty.")
334+
min_node = None
335+
idx, min_idx = 0, None
336+
for tree in self.root_list:
337+
if ((min_node is None) or
338+
(tree is not None and tree.root is not None and
339+
min_node.key > tree.root.key)):
340+
min_node = tree.root
341+
min_idx = idx
342+
idx += 1
343+
if kwargs.get('get_index', None) is not None:
344+
return min_node, min_idx
345+
return min_node
346+
347+
def delete_minimum(self):
348+
"""
349+
Deletes the node with minimum key.
350+
"""
351+
min_node, min_idx = self.find_minimum(get_index=True)
352+
child_root_list = []
353+
for k, child in enumerate(min_node.children):
354+
if child is not None:
355+
child_root_list.append(BinomialTree(root=child, order=k))
356+
self.root_list.delete(min_idx)
357+
child_heap = BinomialHeap(root_list=child_root_list)
358+
self.merge(child_heap)
359+
360+
@property
361+
def is_empty(self):
362+
return self.root_list._last_pos_filled == -1
363+
364+
def decrease_key(self, node, new_key):
365+
"""
366+
Decreases the key of the given node.
367+
368+
Parameters
369+
==========
370+
371+
node: BinomialTreeNode
372+
The node whose key is to be reduced.
373+
new_key
374+
The new key of the given node,
375+
should be less than the current key.
376+
"""
377+
if node.key <= new_key:
378+
raise ValueError("The new key "
379+
"should be less than current node's key.")
380+
node.key = new_key
381+
while ((not node.is_root) and
382+
(node.parent.key > node.key)):
383+
node.parent.key, node.key = \
384+
node.key, node.parent.key
385+
node.parent.data, node.data = \
386+
node.data, node.parent.data
387+
node = node.parent
388+
389+
def delete(self, node):
390+
"""
391+
Deletes the given node.
392+
393+
Parameters
394+
==========
395+
396+
node: BinomialTreeNode
397+
The node which is to be deleted.
398+
"""
399+
self.decrease_key(node, self.find_minimum().key - 1)
400+
self.delete_minimum()

‎pydatastructs/trees/tests/test_heaps.py‎

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
from pydatastructs.trees.heaps import BinaryHeap
2-
from pydatastructs.utils.misc_util import TreeNode
1+
from pydatastructs.trees.heaps import BinaryHeap, BinomialHeap
2+
from pydatastructs.miscellaneous_data_structures.binomial_trees import BinomialTree
3+
from pydatastructs.utils.misc_util import TreeNode, BinomialTreeNode
34
from pydatastructs.utils.raises_util import raises
5+
from collections import deque as Queue
46

57
def test_BinaryHeap():
68

@@ -44,3 +46,94 @@ def test_BinaryHeap():
4446
expected_sorted_elements = [2, 3, 7, 17, 19, 25, 36, 100]
4547
sorted_elements = [min_heap.extract().key for _ in range(8)]
4648
assert expected_sorted_elements == sorted_elements
49+
50+
def test_BinomialHeap():
51+
52+
# Corner cases
53+
assert raises(TypeError, lambda:
54+
BinomialHeap(
55+
root_list=[BinomialTreeNode(1, 1), None])
56+
) is True
57+
tree1 = BinomialTree(BinomialTreeNode(1, 1), 0)
58+
tree2 = BinomialTree(BinomialTreeNode(2, 2), 0)
59+
bh = BinomialHeap(root_list=[tree1, tree2])
60+
assert raises(TypeError, lambda:
61+
bh.merge_tree(BinomialTreeNode(2, 2), None))
62+
assert raises(TypeError, lambda:
63+
bh.merge(None))
64+
65+
# Testing BinomialHeap.merge
66+
nodes = [BinomialTreeNode(1, 1), # 0
67+
BinomialTreeNode(3, 3), # 1
68+
BinomialTreeNode(9, 9), # 2
69+
BinomialTreeNode(11, 11), # 3
70+
BinomialTreeNode(6, 6), # 4
71+
BinomialTreeNode(14, 14), # 5
72+
BinomialTreeNode(2, 2), # 6
73+
BinomialTreeNode(7, 7), # 7
74+
BinomialTreeNode(4, 4), # 8
75+
BinomialTreeNode(8, 8), # 9
76+
BinomialTreeNode(12, 12), # 10
77+
BinomialTreeNode(10, 10), # 11
78+
BinomialTreeNode(5, 5), # 12
79+
BinomialTreeNode(21, 21)] # 13
80+
81+
nodes[2].add_children(nodes[3])
82+
nodes[4].add_children(nodes[5])
83+
nodes[6].add_children(nodes[9], nodes[8], nodes[7])
84+
nodes[7].add_children(nodes[11], nodes[10])
85+
nodes[8].add_children(nodes[12])
86+
nodes[10].add_children(nodes[13])
87+
88+
tree11 = BinomialTree(nodes[0], 0)
89+
tree12 = BinomialTree(nodes[2], 1)
90+
tree13 = BinomialTree(nodes[6], 3)
91+
tree21 = BinomialTree(nodes[1], 0)
92+
93+
heap1 = BinomialHeap(root_list=[tree11, tree12, tree13])
94+
heap2 = BinomialHeap(root_list=[tree21])
95+
96+
def bfs(heap):
97+
bfs_trav = []
98+
for i in range(heap.root_list._last_pos_filled + 1):
99+
layer = []
100+
bfs_q = Queue()
101+
bfs_q.append(heap.root_list[i].root)
102+
while len(bfs_q) != 0:
103+
curr_node = bfs_q.popleft()
104+
if curr_node is not None:
105+
layer.append(curr_node.key)
106+
for _i in range(curr_node.children._last_pos_filled + 1):
107+
bfs_q.append(curr_node.children[_i])
108+
if layer != []:
109+
bfs_trav.append(layer)
110+
return bfs_trav
111+
112+
heap1.merge(heap2)
113+
expected_bfs_trav = [[1, 3, 9, 11], [2, 8, 4, 7, 5, 10, 12, 21]]
114+
assert bfs(heap1) == expected_bfs_trav
115+
116+
# Testing Binomial.find_minimum
117+
assert heap1.find_minimum().key == 1
118+
119+
# Testing Binomial.delete_minimum
120+
heap1.delete_minimum()
121+
assert bfs(heap1) == [[3], [9, 11], [2, 8, 4, 7, 5, 10, 12, 21]]
122+
assert raises(ValueError, lambda: heap1.decrease_key(nodes[3], 15))
123+
heap1.decrease_key(nodes[3], 0)
124+
assert bfs(heap1) == [[3], [0, 9], [2, 8, 4, 7, 5, 10, 12, 21]]
125+
heap1.delete(nodes[12])
126+
assert bfs(heap1) == [[3, 8], [0, 9, 2, 7, 4, 10, 12, 21]]
127+
128+
# Testing BinomialHeap.insert
129+
heap = BinomialHeap()
130+
assert raises(ValueError, lambda: heap.find_minimum())
131+
heap.insert(1, 1)
132+
heap.insert(3, 3)
133+
heap.insert(6, 6)
134+
heap.insert(9, 9)
135+
heap.insert(14, 14)
136+
heap.insert(11, 11)
137+
heap.insert(2, 2)
138+
heap.insert(7, 7)
139+
assert bfs(heap) == [[1, 3, 6, 2, 9, 7, 11, 14]]

‎pydatastructs/utils/__init__.py‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from . import misc_util
44
from .misc_util import (
55
TreeNode,
6-
LinkedListNode
6+
LinkedListNode,
7+
BinomialTreeNode
78
)
89
__all__.extend(misc_util.__all__)

‎pydatastructs/utils/misc_util.py‎

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,19 @@
1-
2-
31
__all__ = [
42
'TreeNode',
5-
'LinkedListNode'
3+
'LinkedListNode',
4+
'BinomialTreeNode'
65
]
76

87
_check_type = lambda a, t: isinstance(a, t)
98
NoneType = type(None)
109

11-
class TreeNode(object):
10+
class Node(object):
11+
"""
12+
Abstract class representing a node.
13+
"""
14+
pass
15+
16+
class TreeNode(Node):
1217
"""
1318
Represents node in trees.
1419
@@ -42,19 +47,70 @@ def __str__(self):
4247
"""
4348
return str((self.left, self.key, self.data, self.right))
4449

45-
class LinkedListNode(object):
50+
class BinomialTreeNode(TreeNode):
4651
"""
47-
Represents node in linked lists.
52+
Represents node in binomial trees.
4853
4954
Parameters
5055
==========
5156
5257
data
5358
Any valid data to be stored in the node.
59+
key
60+
Required for comparison operations.
61+
62+
Note
63+
====
64+
65+
The following are the data members of the class:
66+
67+
parent: BinomialTreeNode
68+
A reference to the BinomialTreeNode object
69+
which is a prent of this.
70+
children: DynamicOneDimensionalArray
71+
An array of references to BinomialTreeNode objects
72+
which are children this node.
73+
is_root: bool, by default, False
74+
If the current node is a root of the tree then
75+
set it to True otherwise False.
5476
"""
77+
__slots__ = ['parent', 'key', 'children', 'data', 'is_root']
5578

56-
# __slots__ = ['data']
79+
def __new__(cls, key, data):
80+
from pydatastructs.linear_data_structures.arrays import DynamicOneDimensionalArray
81+
obj = object.__new__(cls)
82+
obj.data, obj.key = data, key
83+
obj.children, obj.parent, obj.is_root = (
84+
DynamicOneDimensionalArray(BinomialTreeNode, 0),
85+
None,
86+
False
87+
)
88+
return obj
5789

90+
def add_children(self, *children):
91+
"""
92+
Adds children of current node.
93+
"""
94+
for child in children:
95+
self.children.append(child)
96+
child.parent = self
97+
98+
def __str__(self):
99+
"""
100+
For printing the key and data.
101+
"""
102+
return str((self.key, self.data))
103+
104+
class LinkedListNode(Node):
105+
"""
106+
Represents node in linked lists.
107+
108+
Parameters
109+
==========
110+
111+
data
112+
Any valid data to be stored in the node.
113+
"""
58114
def __new__(cls, data=None, links=['next'], addrs=[None]):
59115
obj = object.__new__(cls)
60116
obj.data = data

0 commit comments

Comments
 (0)
Please sign in to comment.