diff --git a/lectures/24_priority_queues_II/README.md b/lectures/24_priority_queues_II/README.md index bf65dbf..1ae121c 100644 --- a/lectures/24_priority_queues_II/README.md +++ b/lectures/24_priority_queues_II/README.md @@ -112,7 +112,102 @@ organized heap data, but incur a O(n log n) cost. Why? - Heap Sort is a simple algorithm to sort a vector of values: Build a heap and then run n consecutive pop operations, storing each “popped” value in a new vector. - It is straightforward to show that this requires O(n log n) time. -- Exercise: Implement an in-place heap sort. An in-place algorithm uses only the memory holding the input data – a separate large temporary vector is not needed. +- Heap sort is an in-place sort. An in-place algorithm uses only the memory holding the input data – a separate large temporary vector is not needed. +- The following is the sort algorithm with a main function to test it; the code makes a min heap. + +```cpp +/* The heapify function is designed to ensure that a subtree rooted at a given index i + * in an array representation of a min heap maintains the heap property. + * + * Why Not Just Heapify Once? A single call to heapify on the entire array + * wouldn't suffice because heapify is designed to correct violations of + * the heap property starting from a specific node, assuming its subtrees are already heaps. + * Initially, the array doesn't have this structure, so multiple calls are necessary to build the initial min-heap. + * Similarly, during the sorting phase, each extraction disrupts the heap structure, necessitating a call to heapify to restore order. + */ +void heapify(std::vector& nums, int n, int i){ + int smallest = i; // assuming i is the smallest + int left = 2*i+1; // i's left child is at this location + int right = 2*i+2; // i's right child is at this location + + if(left sortArray(std::vector& nums) { + int n = nums.size(); + // build the heap, starting from the last non-leaf node. + // why we start from the last non-leaf node? because leaf nodes inherently satisfy the heap property, as they have no children. + // By beginning the heapify process from the last non-leaf node and moving upwards: + // We ensure that when we heapify a node, its children are already heapified. + // This bottom-up approach guarantees that each subtree satisfies the heap property before moving to the next node. + for(int i=n/2-1; i>=0; i--){ + // heapify the subtree whose root is at i + // i.e., build a min heap, with i being the root; and this heap contains nodes from i to n-1; + heapify(nums, n, i); + } + + // now the first one is the largest, swap it to the back + // do this n-1 times. + for(int i=0; i<(n-1); i++){ + // build the min heap again, with 0 being the root. + // but only consider n-1-i elements, as the others are already in the right place. + heapify(nums, n-1-i, 0); + } + + return nums; +} + +// Assuming the heapify and sortArray functions are defined above or included from another file +int main() { + // Sample data to be sorted + std::vector nums = {42, 12, 13, 65, 98, 45, 97, 85, 76, 90}; + + // Output the original array + std::cout << "Original array:\n"; + for (int num : nums) { + std::cout << num << " "; + } + std::cout << std::endl; + + // Sort the array using your sortArray function + std::vector sortedNums = sortArray(nums); + + // Output the sorted array + std::cout << "\nSorted array:\n"; + for (int num : sortedNums) { + std::cout << num << " "; + } + std::cout << std::endl; + + return 0; +} +``` + +The above program prints the following: + +```console +$ g++ heap_sort.cpp +$ ./a.out +Original array: +42 12 13 65 98 45 97 85 76 90 + +Sorted array: +12 42 13 65 90 45 97 85 76 98 +``` ## 24.9 Summary Notes about Vector-Based Priority Queues diff --git a/lectures/24_priority_queues_II/heap_sort.cpp b/lectures/24_priority_queues_II/heap_sort.cpp new file mode 100644 index 0000000..0a0b325 --- /dev/null +++ b/lectures/24_priority_queues_II/heap_sort.cpp @@ -0,0 +1,101 @@ +#include +#include + +/* The heapify function is designed to ensure that a subtree rooted at a given index i + * in an array representation of a heap maintains the heap property. + * While the function doesn't have an explicit base case like some recursive functions, + * it inherently terminates due to the following conditions: + * + * Leaf Node Condition: If the node at index i is a leaf node (i.e., it has no children), + * the function reaches a point where both left and right indices are greater than or equal to n (the size of the heap). + * In this scenario, the conditions left < n and right < n in the if statements evaluating the children will both be false, + * preventing further recursive calls. + * + * Heap Property Satisfaction: If the node at index i is greater than or equal to its children (or if it has no children), + * the heap property is already satisfied. Consequently, the variable largest remains equal to i, + * and the condition largest != i evaluates to false. + * This prevents the swap operation and the subsequent recursive call, leading to termination. + * In essence, the function will return when: + * The node is a leaf node. + * The node's value is greater than or equal to its children's values, maintaining the heap property. + * These implicit conditions ensure that the recursion does not continue indefinitely. + * + * Why Not Just Heapify Once? A single call to heapify on the entire array + * wouldn't suffice because heapify is designed to correct violations of + * the heap property starting from a specific node, assuming its subtrees are already heaps. + * Initially, the array doesn't have this structure, so multiple calls are necessary to build the initial max-heap. + * Similarly, during the sorting phase, each extraction disrupts the heap structure, necessitating a call to heapify to restore order. + * */ + +void heapify(std::vector& nums, int n, int i){ + int smallest = i; // assuming i is the smallest + int left = 2*i+1; // i's left child is at this location + int right = 2*i+2; // i's right child is at this location + + if(left sortArray(std::vector& nums) { + int n = nums.size(); + // build the heap, starting from the last non-leaf node. + // why we start from the last non-leaf node? because leaf nodes inherently satisfy the heap property, as they have no children. + // By beginning the heapify process from the last non-leaf node and moving upwards: + // We ensure that when we heapify a node, its children are already heapified. + // This bottom-up approach guarantees that each subtree satisfies the heap property before moving to the next node. + for(int i=n/2-1; i>=0; i--){ + // heapify the subtree whose root is at i + // i.e., build a max heap, with i being the root; and this heap contains nodes from i to n-1; + heapify(nums, n, i); + } + + // now the first one is the largest, swap it to the back + // do this n-1 times. + for(int i=0; i<(n-1); i++){ + // nums[0] is always the largest one + std::swap(nums[0], nums[n-1-i]); + // build the max heap again, with 0 being the root. + // but only consider n-1-i elements, as the others are already in the right place. + heapify(nums, n-1-i, 0); + } + + return nums; +} + +// Assuming the heapify and sortArray functions are defined above or included from another file +int main() { + // Sample data to be sorted + std::vector nums = {42, 12, 13, 65, 98, 45, 97, 85, 76, 90}; + + // Output the original array + std::cout << "Original array:\n"; + for (int num : nums) { + std::cout << num << " "; + } + std::cout << std::endl; + + // Sort the array using your sortArray function + std::vector sortedNums = sortArray(nums); + + // Output the sorted array + std::cout << "\nSorted array:\n"; + for (int num : sortedNums) { + std::cout << num << " "; + } + std::cout << std::endl; + + return 0; +}