From 80ffd18be89b221fec28e789011fd0d04bdfb5a1 Mon Sep 17 00:00:00 2001 From: Lusina <12752833+BrianLusina@users.noreply.github.com> Date: Wed, 11 Feb 2026 21:06:42 +0300 Subject: [PATCH] feat(datastructures, trees): binary tree branch sum and node depth --- datastructures/trees/binary/node.py | 6 +- .../trees/binary/tree/binary_tree.py | 141 ++++++++++++++++++ datastructures/trees/heaps/__init__.py | 42 ++++-- .../trees/heaps/binary/max_heap/__init__.py | 2 +- .../heaps/binary/max_heap/max_array_heap.py | 54 +++---- .../binary/max_heap/test_max_array_heap.py | 14 +- .../trees/heaps/binary/min_heap/__init__.py | 4 +- .../heaps/binary/min_heap/min_array_heap.py | 136 ++++++++++++----- .../binary/min_heap/test_min_array_heap.py | 58 +++++-- datastructures/trees/heaps/node.py | 7 +- datastructures/trees/heaps/utils.py | 56 +++++++ 11 files changed, 413 insertions(+), 107 deletions(-) diff --git a/datastructures/trees/binary/node.py b/datastructures/trees/binary/node.py index 9284055c..3421aa76 100644 --- a/datastructures/trees/binary/node.py +++ b/datastructures/trees/binary/node.py @@ -17,7 +17,7 @@ def __init__( right: Optional["BinaryTreeNode"] = None, key: Optional[Any] = None, parent: Optional["BinaryTreeNode"] = None, - next: Optional["BinaryTreeNode"] = None, + nxt: Optional["BinaryTreeNode"] = None, ) -> None: """ Constructor for BinaryTreeNode class. This will create a new node with the provided data and optional @@ -30,7 +30,7 @@ def __init__( right (Optional[BinaryTreeNode]): Right child of the node key (Optional[Any]): Key for the node, if not provided a hash of the data is used parent (Optional[BinaryTreeNode]): Parent of the node - next (Optional[BinaryTreeNode]): Next child of the node which is the sibling of the node. The sibling is the + nxt (Optional[BinaryTreeNode]): Next child of the node which is the sibling of the node. The sibling is the node on the same level as this node. If this is the rightmost node in the tree that is not on the last level of the tree, then this is the next node on the next level starting from the left. If this is the last node in the tree, then this is None. @@ -42,7 +42,7 @@ def __init__( # node on a given level, then it is connected to the first node on the next level. If this node is the last node # in the tree on the last node, then it is pointed to None. By default, it is set to None. # Note that if this is the root node, it is connected to the left most node on the next level. - self.next: Optional[BinaryTreeNode] = next + self.next: Optional[BinaryTreeNode] = nxt def insert_node(self, data: T) -> None: """ diff --git a/datastructures/trees/binary/tree/binary_tree.py b/datastructures/trees/binary/tree/binary_tree.py index 5418f41c..d4a804f0 100644 --- a/datastructures/trees/binary/tree/binary_tree.py +++ b/datastructures/trees/binary/tree/binary_tree.py @@ -960,3 +960,144 @@ def longest_uni_value_path(self) -> int: The length of the path between two nodes is represented by the number of edges between them. """ return longest_uni_value_path(self.root) + + def branch_sum(self) -> List[Any]: + """ + Retrieves the sum of branches ordered from left most branch sum to right most branch sum. A branch sum is the + sum of all values in a binary tree branch. A binary tree branch is a path of nodes in a tree that starts at the + root node and ends at any leaf node. + + For example given the tree below + + 1 + / \ + 2 3 + / \ / \ + 4 5 6 7 + / \ / + 8 9 10 + + The output should be: + [15, 16, 18, 10, 11] + + 15 == 1 + 2 + 4 + 8 + 16 == 1 + 2 + 4 + 9 + 18 == 1 + 2 + 5 + 10 + 10 == 1 + 3 + 6 + 11 == 1 + 3 + 7 + + The time complexity for the solution is O(n) because we have to traverse each element in the tree and the space + complexity is also O(n) because of the recursion. + """ + if not self.root: + return [] + + branch_totals = [] + + def preorder_traversal( + node: Optional[BinaryTreeNode], running_sum: Any + ) -> None: + nonlocal branch_totals + if not node: + return + + total_sum = running_sum + node.data + if not node.left and not node.right: + branch_totals.append(total_sum) + + preorder_traversal(node.left, total_sum) + preorder_traversal(node.right, total_sum) + + preorder_traversal(self.root, 0) + return branch_totals + + def sum_of_node_depths(self) -> int: + """ + Returns the sum of depths of all nodes in the binary tree. + Node Depth is the distance between a node in a binary tree and the tree's root. + For example given the tree below + + 1 + / \ + 2 3 + / \ / \ + 4 5 6 7 + / \ + 8 9 + Output should be: 16 + The depth of node with value 2 is 1 + The depth of node with value 3 is 1 + The depth of node with value 4 is 2 + The depth of node with value 5 is 2 + etc ... + Summing all of these depths yields 16 + + This uses an iterative approach with a stack to get the sum of all the node depths. The stack will store the + node alongside the depth of the node and be used to calculate the total depth. This incurs a time complexity + cost of O(n) as we traverse all the nodes of the tree and space complexity cost of O(h) where h is the height + of the tree as we store all the nodes in the stack. + + Returns: + int: Sum of depths of all nodes in the binary tree. + """ + + if not self.root: + return 0 + + stack: List[Tuple[BinaryTreeNode, int]] = [(self.root, 0)] + total_depth: int = 0 + + while stack: + node, depth = stack.pop() + total_depth += depth + if node.left: + stack.append((node.left, depth + 1)) + if node.right: + stack.append((node.right, depth + 1)) + + return total_depth + + def sum_of_node_depths_recursive(self) -> int: + """ + Returns the sum of depths of all nodes in the binary tree using recursion. + Node Depth is the distance between a node in a binary tree and the tree's root. + For example given the tree below + + 1 + / \ + 2 3 + / \ / \ + 4 5 6 7 + / \ + 8 9 + Output should be: 16 + The depth of node with value 2 is 1 + The depth of node with value 3 is 1 + The depth of node with value 4 is 2 + The depth of node with value 5 is 2 + etc ... + Summing all of these depths yields 16 + + This uses a recursive approach to get the sum of all the node depths. This incurs a time complexity + cost of O(n) as we traverse all the nodes of the tree and space complexity cost of O(h) where h is the height + of the tree as we call the recursion stack h times on the height of the tree. + + Returns: + int: Sum of depths of all nodes in the binary tree. + """ + + if not self.root: + return 0 + + def sum_of_node_depths_helper( + node: Optional[BinaryTreeNode], depth: int + ) -> int: + if not node: + return 0 + return ( + depth + + sum_of_node_depths_helper(node.left, depth) + + sum_of_node_depths_helper(node.right, depth) + ) + + return sum_of_node_depths_helper(self.root, 0) diff --git a/datastructures/trees/heaps/__init__.py b/datastructures/trees/heaps/__init__.py index 97de90a3..6c5ec633 100644 --- a/datastructures/trees/heaps/__init__.py +++ b/datastructures/trees/heaps/__init__.py @@ -1,5 +1,5 @@ from abc import ABC, abstractmethod -from typing import Any, List +from typing import Any, List, Optional class Heap(ABC): @@ -8,12 +8,19 @@ class Heap(ABC): """ @abstractmethod - def insert_data(self, data: Any): + def insert(self, data: Any): """ Inserts a data value into the heap """ raise NotImplementedError("Not yet implemented") + @abstractmethod + def peak(self) -> Optional[Any]: + """ + Returns data at the top of the heap without removing it + """ + raise NotImplementedError("Not yet implemented") + @abstractmethod def delete(self) -> Any: """ @@ -38,25 +45,34 @@ def get_right_child_index(i: int) -> int: class ArrayBasedHeap(Heap): """ - Heap datastructure that uses an array as the underlying datastructure to build a heap. + Heap data structure that uses an array as the underlying data structure to build a heap. """ - def __init__(self): + def __init__(self, data: Optional[List[Any]] = None): super().__init__() - self.data: List[Any] = [] + if data is None: + data = [] + self.heap: List[Any] = data def __len__(self): - return len(self.data) + return len(self.heap) @property - def root_node(self): + def root_node(self) -> Optional[Any]: + """ + Retrieves the root node of the Heap + """ + if len(self.heap) == 0: + raise Exception("Heap is empty") + return self.heap[0] + + def peak(self) -> Optional[Any]: """ Retrieves the root node of the Heap - :return: """ - if len(self.data) == 0: + if len(self.heap) == 0: raise Exception("Heap is empty") - return self.data[0] + return self.heap[0] @property def last_node(self): @@ -64,11 +80,11 @@ def last_node(self): Returns the last node of the heap :return: """ - if len(self.data) == 0: + if len(self.heap) == 0: raise Exception("Heap is empty") - return self.data[len(self.data) - 1] + return self.heap[len(self.heap) - 1] - def insert_data(self, data: Any): + def insert(self, data: Any): """ Inserts a value into the heap :param data: element to insert into the heap diff --git a/datastructures/trees/heaps/binary/max_heap/__init__.py b/datastructures/trees/heaps/binary/max_heap/__init__.py index e18a09b0..8930a221 100644 --- a/datastructures/trees/heaps/binary/max_heap/__init__.py +++ b/datastructures/trees/heaps/binary/max_heap/__init__.py @@ -91,7 +91,7 @@ def __build_heap(self, array: List[HeapNode]) -> List[HeapNode]: return array - def insert_data(self, data: Any): + def insert(self, data: Any): """ Inserts a value into the heap """ diff --git a/datastructures/trees/heaps/binary/max_heap/max_array_heap.py b/datastructures/trees/heaps/binary/max_heap/max_array_heap.py index b5315079..42734a8b 100644 --- a/datastructures/trees/heaps/binary/max_heap/max_array_heap.py +++ b/datastructures/trees/heaps/binary/max_heap/max_array_heap.py @@ -11,42 +11,42 @@ class MaxArrayBasedHeap(ArrayBasedHeap): def __init__(self): super().__init__() - def insert_data(self, value: Any): + def insert(self, value: Any): """ Inserts a value into the heap """ # add the value into the last node - self.data.append(value) + self.heap.append(value) # keep track of the index of the newly inserted node - new_node_index = len(self.data) - 1 + new_node_index = len(self.heap) - 1 # the following executes the "trickle up" algorithm. If the new node is not in the root position and it's greater # than its parent node while ( new_node_index > 0 - and self.data[new_node_index] - > self.data[self.get_parent_index(new_node_index)] + and self.heap[new_node_index] + > self.heap[self.get_parent_index(new_node_index)] ): # swap the new node with the parent node ( - self.data[self.get_parent_index(new_node_index)], - self.data[new_node_index], + self.heap[self.get_parent_index(new_node_index)], + self.heap[new_node_index], ) = ( - self.data[new_node_index], - self.data[self.get_parent_index(new_node_index)], + self.heap[new_node_index], + self.heap[self.get_parent_index(new_node_index)], ) # update the index of the new node new_node_index = self.get_parent_index(new_node_index) def delete(self) -> Any: - if len(self.data) == 0: + if len(self.heap) == 0: raise Exception("Heap is empty") # we only ever delete the root node from a heap, so we pop the last node from the array and make it the root node - root_node = self.data[0] - self.data[0] = self.data.pop() + root_node = self.heap[0] + self.heap[0] = self.heap.pop() # track the current index of the "trickle node". This is the node that will be moved into the correct position trickle_node_index = 0 @@ -57,9 +57,9 @@ def delete(self) -> Any: larger_child_index = self.__calculate_larger_child_index(trickle_node_index) # swap the trickle node with its larger child - self.data[trickle_node_index], self.data[larger_child_index] = ( - self.data[larger_child_index], - self.data[trickle_node_index], + self.heap[trickle_node_index], self.heap[larger_child_index] = ( + self.heap[larger_child_index], + self.heap[trickle_node_index], ) trickle_node_index = larger_child_index @@ -76,20 +76,20 @@ def __has_greater_child(self, index: int) -> bool: left_child_index = self.get_left_child_index(index) right_child_index = self.get_right_child_index(index) - left_child_exists = left_child_index < len(self.data) - right_child_exists = right_child_index < len(self.data) + left_child_exists = left_child_index < len(self.heap) + right_child_exists = right_child_index < len(self.heap) if left_child_exists and right_child_exists: - left_child = self.data[left_child_index] - right_child = self.data[right_child_index] + left_child = self.heap[left_child_index] + right_child = self.heap[right_child_index] - return left_child > self.data[index] or right_child > self.data[index] + return left_child > self.heap[index] or right_child > self.heap[index] elif left_child_exists and not right_child_exists: - left_child = self.data[left_child_index] - return left_child > self.data[index] + left_child = self.heap[left_child_index] + return left_child > self.heap[index] elif right_child_exists and not left_child_exists: - right_child = self.data[right_child_index] - return right_child > self.data[index] + right_child = self.heap[right_child_index] + return right_child > self.heap[index] else: return False @@ -100,14 +100,14 @@ def __calculate_larger_child_index(self, index: int) -> int: :return: The position of the larger child """ # if there is no right child - if not self.data[self.get_right_child_index(index)]: + if not self.heap[self.get_right_child_index(index)]: # return the left child index return self.get_left_child_index(index) # if right child value is greater than left child value if ( - self.data[self.get_right_child_index(index)] - > self.data[self.get_left_child_index(index)] + self.heap[self.get_right_child_index(index)] + > self.heap[self.get_left_child_index(index)] ): # return the right child index return self.get_right_child_index(index) diff --git a/datastructures/trees/heaps/binary/max_heap/test_max_array_heap.py b/datastructures/trees/heaps/binary/max_heap/test_max_array_heap.py index 4b74bb9e..a70911f6 100644 --- a/datastructures/trees/heaps/binary/max_heap/test_max_array_heap.py +++ b/datastructures/trees/heaps/binary/max_heap/test_max_array_heap.py @@ -9,13 +9,13 @@ def setUp(self) -> None: def test_insertion(self): """should insert nodes in the correct position in the heap""" - self.heap.insert_data(8) - self.heap.insert_data(3) - self.heap.insert_data(2) - self.heap.insert_data(7) - self.heap.insert_data(9) - self.heap.insert_data(1) - self.heap.insert_data(4) + self.heap.insert(8) + self.heap.insert(3) + self.heap.insert(2) + self.heap.insert(7) + self.heap.insert(9) + self.heap.insert(1) + self.heap.insert(4) actual = self.heap.root_node actual_last_node = self.heap.last_node expected = 9 diff --git a/datastructures/trees/heaps/binary/min_heap/__init__.py b/datastructures/trees/heaps/binary/min_heap/__init__.py index 6e5465a5..f94d96db 100644 --- a/datastructures/trees/heaps/binary/min_heap/__init__.py +++ b/datastructures/trees/heaps/binary/min_heap/__init__.py @@ -1,5 +1,5 @@ from heapq import heapify, heappop -from typing import Any, List, Union +from typing import Any, List, Union, Optional from datastructures.trees.heaps import Heap from datastructures.trees.heaps.node import HeapNode @@ -93,7 +93,7 @@ def build_heap(self, array: List[HeapNode]) -> List[HeapNode]: return array - def insert_data(self, data: Any): + def insert(self, data: Any): """ Inserts a value into the heap """ diff --git a/datastructures/trees/heaps/binary/min_heap/min_array_heap.py b/datastructures/trees/heaps/binary/min_heap/min_array_heap.py index eb457b30..06a55c41 100644 --- a/datastructures/trees/heaps/binary/min_heap/min_array_heap.py +++ b/datastructures/trees/heaps/binary/min_heap/min_array_heap.py @@ -1,55 +1,114 @@ -from typing import Any +from typing import Any, List, Optional +from algorithms.sorting.heapsort import heapsort, heapify, right_child_index from datastructures.trees.heaps import ArrayBasedHeap +from datastructures.trees.heaps.utils import has_smaller_child class MinArrayBasedHeap(ArrayBasedHeap): """ - Min Heap that uses an array as the underlying datastructure of a heap + Min Heap that uses an array as the underlying data structure of a heap """ - def __init__(self): - super().__init__() + def __init__(self, data: Optional[List[Any]] = None): + built_heap = self.build_heap(data) + super().__init__(built_heap) - def insert_data(self, value: Any): + @staticmethod + def swap(i: int, j: int, data: List[Any]) -> None: + """ + Swaps the elements at index i and j in place + Args: + i (int): index of element to swap + j (int): index of element to swap + data (List[Any]): data to swap + Returns: + None + """ + data[i], data[j] = data[j], data[i] + + def bubble_up(self, current_idx: int, data: List[Any]) -> None: + """ + Bubble up one element at index current_idx. Incurs time complexity of O(log n) and space of O(1) + Args: + current_idx (int): index of element to bubble up + data (List[Any]): data to bubble + Returns: + None + """ + parent_idx = self.get_parent_index(current_idx) + while current_idx > 0 and data[current_idx] < data[parent_idx]: + self.swap(current_idx, parent_idx, data) + current_idx = parent_idx + parent_idx = (current_idx - 1) // 2 + + def bubble_down(self, current_idx: int, end_idx: int, data: List[Any]) -> None: + left_idx = self.get_left_child_index(current_idx) + while left_idx <= end_idx: + idx = self.get_right_child_index(current_idx) + right_idx = idx if idx <= end_idx else -1 + if right_idx != -1 and data[right_idx] < data[left_idx]: + idx_to_swap = right_idx + else: + idx_to_swap = left_idx + if data[idx_to_swap] < data[current_idx]: + self.swap(current_idx, idx_to_swap, data) + current_idx = idx_to_swap + left_idx = self.get_left_child_index(current_idx) + else: + break + + def build_heap(self, data: List[Any]) -> List[Any]: + """ + Build a heap from a list + """ + if not data: + return [] + first_parent_idx = (len(data) - 2) // 2 + + for currentIdx in reversed(range(first_parent_idx + 1)): + self.bubble_down(currentIdx, len(data) - 1, data) + return data + + def insert(self, value: Any): """ Inserts a value into the heap """ # add the value into the last node - self.data.append(value) + self.heap.append(value) # keep track of the index of the newly inserted node - new_node_index = len(self.data) - 1 + new_node_index = len(self.heap) - 1 # the following executes the "trickle down" algorithm. If the new node is not in the root position and it's less # than its parent node while ( new_node_index > 0 - and self.data[new_node_index] - < self.data[self.get_parent_index(new_node_index)] + and self.heap[new_node_index] + < self.heap[self.get_parent_index(new_node_index)] ): # swap the new node with the parent node ( - self.data[self.get_parent_index(new_node_index)], - self.data[new_node_index], + self.heap[self.get_parent_index(new_node_index)], + self.heap[new_node_index], ) = ( - self.data[new_node_index], - self.data[self.get_parent_index(new_node_index)], + self.heap[new_node_index], + self.heap[self.get_parent_index(new_node_index)], ) # update the index of the new node new_node_index = self.get_parent_index(new_node_index) def delete(self) -> Any: - if len(self.data) == 0: + if len(self.heap) == 0: raise Exception("Heap is empty") # we only ever delete the root node from a heap, so we pop the last node from the array and make it the root node - root_node = self.data[0] - # note that this operation of popping an element from the last item in a list is O(1) operation, however, + root_node = self.heap[0] + # Note that this operation of popping an element from the last item in a list is O(1) operation, however, # making the first item in the list the popped element is an O(n) operation where n is the size of the list. This # is because the system has to "shift" each element in the array to the right - self.data[0] = self.data.pop() + self.heap[0] = self.heap.pop() # track the current index of the "trickle node". This is the node that will be moved into the correct position trickle_node_index = 0 @@ -62,9 +121,9 @@ def delete(self) -> Any: ) # swap the trickle node with its smaller child - self.data[trickle_node_index], self.data[smaller_child_index] = ( - self.data[smaller_child_index], - self.data[trickle_node_index], + self.heap[trickle_node_index], self.heap[smaller_child_index] = ( + self.heap[smaller_child_index], + self.heap[trickle_node_index], ) trickle_node_index = smaller_child_index @@ -78,23 +137,23 @@ def __has_smaller_child(self, index: int) -> bool: :param index: the index to check for :return: True if the condition is met, false otherwise """ - left_child_index = self.get_left_child_index(index) - right_child_index = self.get_right_child_index(index) + left_child_idx = self.get_left_child_index(index) + right_child_idx = self.get_right_child_index(index) - left_child_exists = left_child_index < len(self.data) - right_child_exists = right_child_index < len(self.data) + left_child_exists = left_child_idx < len(self.heap) + right_child_exists = right_child_idx < len(self.heap) if left_child_exists and right_child_exists: - left_child = self.data[left_child_index] - right_child = self.data[right_child_index] + left_child = self.heap[left_child_idx] + right_child = self.heap[right_child_idx] - return left_child < self.data[index] or right_child < self.data[index] + return left_child < self.heap[index] or right_child < self.heap[index] elif left_child_exists and not right_child_exists: - left_child = self.data[left_child_index] - return left_child < self.data[index] + left_child = self.heap[left_child_idx] + return left_child < self.heap[index] elif right_child_exists and not left_child_exists: - right_child = self.data[right_child_index] - return right_child < self.data[index] + right_child = self.heap[right_child_idx] + return right_child < self.heap[index] else: return False @@ -105,17 +164,20 @@ def __calculate_smaller_child_index(self, index: int) -> int: :return: The position of the larger child """ # if there is no right child - if not self.data[self.get_right_child_index(index)]: + right_child_idx = self.get_right_child_index(index) + left_child_idx = self.get_left_child_index(index) + + if right_child_idx < len(self.heap) and not self.heap[right_child_idx]: # return the left child index - return self.get_left_child_index(index) + return left_child_idx # if right child value is less than left child value if ( - self.data[self.get_right_child_index(index)] - < self.data[self.get_left_child_index(index)] + right_child_idx < len(self.heap) + and self.heap[right_child_idx] < self.heap[left_child_idx] ): # return the left child index - return self.get_left_child_index(index) + return left_child_idx else: # if the left child value is greater or equal to right child, return the right child index. - return self.get_right_child_index(index) + return right_child_idx diff --git a/datastructures/trees/heaps/binary/min_heap/test_min_array_heap.py b/datastructures/trees/heaps/binary/min_heap/test_min_array_heap.py index bb95d568..c8dd4a09 100644 --- a/datastructures/trees/heaps/binary/min_heap/test_min_array_heap.py +++ b/datastructures/trees/heaps/binary/min_heap/test_min_array_heap.py @@ -1,28 +1,60 @@ import unittest -from .min_array_heap import MinArrayBasedHeap +from datastructures.trees.heaps.binary.min_heap.min_array_heap import MinArrayBasedHeap class MaxArrayBasedHeapTestCaseOne(unittest.TestCase): - def setUp(self) -> None: - self.heap = MinArrayBasedHeap() - def test_insertion(self): """should insert nodes in the correct position in the heap""" - self.heap.insert_data(8) - self.heap.insert_data(3) - self.heap.insert_data(2) - self.heap.insert_data(7) - self.heap.insert_data(9) - self.heap.insert_data(1) - self.heap.insert_data(4) - actual = self.heap.root_node - actual_last_node = self.heap.last_node + heap = MinArrayBasedHeap() + heap.insert(8) + heap.insert(3) + heap.insert(2) + heap.insert(7) + heap.insert(9) + heap.insert(1) + heap.insert(4) + actual = heap.root_node + actual_last_node = heap.last_node expected_root_node = 1 expected_last_node = 4 self.assertEqual(expected_root_node, actual) self.assertEqual(expected_last_node, actual_last_node) + def test_insertion_on_empty_data(self): + """should insert nodes in the correct position in the heap""" + heap = MinArrayBasedHeap() + heap.insert(8) + actual = heap.root_node + actual_last_node = heap.last_node + expected_root_node = 8 + expected_last_node = 8 + self.assertEqual(expected_root_node, actual) + self.assertEqual(expected_last_node, actual_last_node) + + def test_initialized_with_data(self): + """should insert nodes in the correct position in the heap when given an array of data""" + initial_data = [48, 12, 24, 7, 8, -5, 24, 391, 24, 56, 2, 6, 8, 41] + expected_data = [-5, 2, 6, 7, 8, 8, 24, 391, 24, 56, 12, 24, 48, 41] + heap = MinArrayBasedHeap(data=initial_data) + actual = heap.root_node + actual_last_node = heap.last_node + expected_root_node = -5 + expected_last_node = 41 + self.assertEqual(expected_root_node, actual) + self.assertEqual(expected_last_node, actual_last_node) + + # insert 76 + heap.insert(76) + actual_peak_neg_5 = heap.peak() + self.assertEqual(-5, actual_peak_neg_5) + + actual_del_neg_5 = heap.delete() + self.assertEqual(-5, actual_del_neg_5) + + actual_peak_2 = heap.peak() + self.assertEqual(2, actual_peak_2) + if __name__ == "__main__": unittest.main() diff --git a/datastructures/trees/heaps/node.py b/datastructures/trees/heaps/node.py index 55613881..ccffcf3f 100644 --- a/datastructures/trees/heaps/node.py +++ b/datastructures/trees/heaps/node.py @@ -1,16 +1,15 @@ from typing import Optional -from datastructures.trees.binary.tree import BinaryTreeNode, T +from datastructures.trees.binary.node import BinaryTreeNode, T class HeapNode(BinaryTreeNode): """ - Represents a Heap node in a Heap datastructure. Heap nodes have either a left or right child so, they inherit from + Represents a Heap node in a Heap data structure. Heap nodes have either a left or a right child, so they inherit from a Binary Tree Node which exhibit similar properties. """ def __init__(self, data: T, key: Optional[T] = None): - super().__init__(data) - self.key = key + super().__init__(data, key=key) @property def name(self): diff --git a/datastructures/trees/heaps/utils.py b/datastructures/trees/heaps/utils.py index dba860b5..e5df805d 100644 --- a/datastructures/trees/heaps/utils.py +++ b/datastructures/trees/heaps/utils.py @@ -1,5 +1,7 @@ +from typing import List, Any from datastructures.trees.heaps import Heap from datastructures.trees.heaps.binary.max_heap import MaxHeap +from algorithms.sorting.heapsort import left_child_index, right_child_index def min_heapify(heap: MaxHeap, idx: int) -> Heap: @@ -39,3 +41,57 @@ def convert_max_to_min(max_heap: MaxHeap) -> MaxHeap: # call minHeapify on all parent nodes max_heap = min_heapify(max_heap, i) return max_heap + + +def has_smaller_child(data: List[Any], index: int) -> bool: + """ + Checks whether a node at the given index has left and right children and if either of those children are less + than the node at the given index. + Args + data(list): Node data + index(int): Node index + Returns: + bool: True if the condition is met, false otherwise + """ + left_child_idx = left_child_index(index) + right_child_idx = right_child_index(index) + + left_child_exists = left_child_idx < len(data) + right_child_exists = right_child_idx < len(data) + + if left_child_exists and right_child_exists: + left_child = data[left_child_idx] + right_child = data[right_child_idx] + + return left_child < data[index] or right_child < data[index] + elif left_child_exists and not right_child_exists: + left_child = data[left_child_idx] + return left_child < data[index] + elif right_child_exists and not left_child_exists: + right_child = data[right_child_idx] + return right_child < data[index] + else: + return False + + +def calculate_smaller_child_index(data: List[Any], index: int) -> int: + """ + Calculates the index of the smaller child of a heap node in the given index position in the min heap. + Args: + data(list): Node data + index(int): Node index + Returns: + int: Index of the smaller child + """ + # if there is no right child + if not data[right_child_index(index)]: + # return the left child index + return left_child_index(index) + + # if right child value is less than left child value + if data[right_child_index(index)] < data[left_child_index(index)]: + # return the left child index + return left_child_index(index) + else: + # if the left child value is greater or equal to right child, return the right child index. + return right_child_index(index)