diff --git a/lib/heap_sort.rb b/lib/heap_sort.rb index c8a32a4..5356787 100644 --- a/lib/heap_sort.rb +++ b/lib/heap_sort.rb @@ -1,8 +1,106 @@ +# # This method uses a heap to sort an array. +# # Time Complexity: O(n log n) to add elements to a heap and swap them in order at each level +# # Space Complexity: O(n) since you're adding all the elements to a heap here +def heapsort(list) + return [] if list.empty? + + min_heap = MinHeap.new() + + list.each do |item| + min_heap.add(item) + end + + sorted_array = [] + + list.length.times do + sorted_item = min_heap.remove() + sorted_array << sorted_item + end + + return sorted_array +end + +def swap(list, index_1, index_2) + temp = list[index_1] + list[index_1] = list[index_2] + list[index_2] = temp +end + + + +# Challenge If you can, do this method in O(1) space complexity. # This method uses a heap to sort an array. -# Time Complexity: ? -# Space Complexity: ? -def heap_sort(list) - raise NotImplementedError, "Method not implemented yet..." +# Time Complexity: O(n log n) to add elements to a heap and swap them in order at each level +# Space Complexity: O(1) +def heapsort_in_place(list) + return [] if list.empty? + + # Build heap (rearrange array) + build_max_heap(list) + + unsorted_last_index = list.length - 1 + + while unsorted_last_index > 0 + # Move current root (largest) to end + swap(list, 0, unsorted_last_index) + + # Max heapify the reduced heap + max_heapify(list, unsorted_last_index, 0) + + # The end is sorted + unsorted_last_index -= 1 + end + + return list +end + + +def build_max_heap(list) + # Range of internal nodes is 0 to (n/2 - 1) and range of leaves is (n/2) to (n - 1) + i = list.length / 2 - 1 + + while i >= 0 do + # Starting from half of array (non-leaf nodes), heapify (make a max heap, moving up) + max_heapify(list, list.length, i) + i -= 1 + end + + return list +end + +# Compare current node to left and right child nodes, and swap to keep max heap structure +def max_heapify(list, size, root) + root_larger_than_children = false + + while !root_larger_than_children + largest = root + + left_child_index = (root * 2) + 1 + right_child_index = (root * 2) + 2 + + left_child_exists = (left_child_index <= (size - 1)) + right_child_exists = (right_child_index <= (size - 1)) + + # If left child is larger than root, make left child the largest + if left_child_exists && list[left_child_index] > list[largest] + largest = left_child_index + end + + # If right child is larger than largest so far, make right child the largest + if right_child_exists && list[right_child_index] > list[largest] + largest = right_child_index + end + + # If largest now is not root, swap + if largest != root + swap(list, largest, root) + + root = largest + next + end + + root_larger_than_children = true + end end \ No newline at end of file diff --git a/lib/min_heap.rb b/lib/min_heap.rb index 6eaa630..8e8c8e0 100644 --- a/lib/min_heap.rb +++ b/lib/min_heap.rb @@ -14,18 +14,28 @@ def initialize end # This method adds a HeapNode instance to the heap - # Time Complexity: ? - # Space Complexity: ? + # Time Complexity: O(log n) to swap for each level + # Space Complexity: O(log n) for the call stack in heap_up?? def add(key, value = key) - raise NotImplementedError, "Method not implemented yet..." + @store << HeapNode.new(key, value) + + heap_up(@store.length - 1) end # This method removes and returns an element from the heap # maintaining the heap structure - # Time Complexity: ? - # Space Complexity: ? + # Time Complexity: O(log n) to swap for each level + # Space Complexity: O(log n) for the call stack in heap_down?? def remove() - raise NotImplementedError, "Method not implemented yet..." + return nil if @store.empty? + + swap(0, @store.length - 1) + result = @store.pop + + # resort min-heap + heap_down(0) + + return result.value end @@ -44,10 +54,10 @@ def to_s end # This method returns true if the heap is empty - # Time complexity: ? - # Space complexity: ? + # Time complexity: O(1) + # Space complexity: O(1) def empty? - raise NotImplementedError, "Method not implemented yet..." + return @store.length == 0 end private @@ -55,19 +65,58 @@ def empty? # This helper method takes an index and # moves it up the heap, if it is less than it's parent node. # It could be **very** helpful for the add method. - # Time complexity: ? - # Space complexity: ? + # Time Complexity: O(log n) to swap for each level + # Space complexity: O(log n) for the call stack def heap_up(index) - + return if index == 0 + + parent_index = (index - 1) / 2 + + if @store[index].key < @store[parent_index].key + swap(index, parent_index) + # re-sort min-heap + heap_up(parent_index) + end end # This helper method takes an index and - # moves it up the heap if it's smaller - # than it's parent node. + # moves it down the heap if it's larger + # than it's children node. def heap_down(index) - raise NotImplementedError, "Method not implemented yet..." + return if is_a_leaf(index) + + smallest_child_index = min_child_index(index) + + if @store[index].key > @store[smallest_child_index].key + swap(index, smallest_child_index) + # resort min-heap + heap_down(smallest_child_index) + end end + # Helper method returns true if the given index is on the last level / is a leaf + def is_a_leaf(index) + left_child_index = (index * 2 ) + 1 + last_index = @store.length - 1 + + return left_child_index > last_index + end + + # Helper method returns the index of the smallest child + def min_child_index(index) + left_child_index = (index * 2) + 1 + right_child_index = (index * 2) + 2 + + left_child_is_last_leaf = (left_child_index == @store.length - 1) + + if ( left_child_is_last_leaf ) || + ( @store[left_child_index].key < @store[right_child_index].key ) + return left_child_index + else + return right_child_index + end + end + # If you want a swap method... you're welcome def swap(index_1, index_2) temp = @store[index_1] diff --git a/test/heapsort_test.rb b/test/heapsort_test.rb index 34402ac..7ce79b7 100644 --- a/test/heapsort_test.rb +++ b/test/heapsort_test.rb @@ -1,6 +1,6 @@ require_relative "test_helper" -xdescribe "heapsort" do +describe "heapsort" do it "sorts an empty array" do # Arrange list = []