By the end of this module, you will be able to:
The Sorting problem:
Let's start by looking at a classic algorithm: BubbleSort
Sweep for i=0: Comparison at position j=4: 40 30 10 50 20 // Need to swap 20 and 50 Comparison at position j=3: 40 30 10 20 50 Comparison at position j=2: 40 30 10 20 50 // Need to swap 10 and 30 Comparison at position j=1: 40 10 30 20 50 // Need to swap 10 and 40 Final array for i=0: 10 40 30 20 50 // Smallest element in correct place (i=0) Sweep for i=1: Comparison at position j=4: 10 40 30 20 50 Comparison at position j=3: 10 40 30 20 50 // Need to swap 20 and 30 Comparison at position j=2: 10 40 20 30 50 // Need to swap 20 and 40 Final array for i=1: 10 20 40 30 50 // Second smallest in correct place Sweep for i=2: Comparison at position j=4: 10 20 40 30 50 Comparison at position j=3: 10 20 40 30 50 // Need to swap 30 and 40 Final array for i=2: 10 20 30 40 50 Sweep for i=3: Comparison at position j=4: 10 20 30 40 50 Final array for i=3: 10 20 30 40 50 Last element stays in place: 10 20 30 40 50
2 4 1 0 3
Algorithm: bubbleSort (data) Input: data - an array of size n 1. for i=0 to n-2 2. for j=n-1 down to i+1 3. if data[j-1] > data[j] 4. swap (data, j-1, j) 5. endif 6. endfor 7. endfor
The code:
public class BubbleSort { public void sortInPlace (int[] data) { // Key idea: Bubble smallest element, then next smallest etc. // 1. For positions i=0,...,length-1, find the element that should be at position i for (int i=0; i < data.length-1; i++){ // 1.1 Scan all elements past i starting from the end: for (int j=data.length-1; j > i; j--) { // 1.1.1 If a pair of consecutive elements is out of // order, swap the elements: if (data[j-1] > data[j]) swap (data, j-1, j); } } } public void sortInPlace (Comparable[] data) { for (int i=0; i < data.length-1; i++){ for (int j=data.length-1; j > i; j--) { // Notice the use of the compareTo method: if (data[j-1].compareTo (data[j]) > 0) swap (data, j-1, j); } } } public int[] createSortIndex (int[] data) { // 1. Create space for a sort index: int[] index = new int [data.length]; // 2. Initialize to current data: for (int i=0; i < data.length; i++) index[i] = i; // 3. Swap indices by comparing data: // ... return index; } void swap (int[] data, int i, int j) { int temp = data[i]; data[i] = data[j]; data[j] = temp; } void swap (Comparable[] data, int i, int j) { // ... } public static void main (String[] argv) { // Testing ... } }
Note:
public class BubbleSort2 { public void sortInPlace (int[] data) { // Key idea: Bubble smallest element, then next smallest etc. // 1. For positions i=0,...,length-1, find the element that belongs at i: for (int i=0; i < data.length-1; i++){ bubble (data, i); } } void bubble (int[] data, int startingPosition) { // Swap down the element that belongs in "startingPosition". for (int j=data.length-1; j > startingPosition; j--) { if (data[j-1] > data[j]) swap (data, j-1, j); } } // ... }This is more readable, but costs an extra method call in each iteration.
public void sortInPlace (int[] data) { for (int i=0; i < data.length-1; i++){ for (int j=data.length-1; j > i; j--) { if (data[j-1] > data[j]) { int temp = data[j-1]; data[j-1] = data[j]; data[j] = temp; } } } }
... public void sortInPlace (Comparable[] data) { for (int i=0; i < data.length-1; i++){ for (int j=data.length-1; j > i; j--) { // Notice the use of the compareTo method: if (data[j-1].compareTo (data[j]) > 0) swap (data, j-1, j); } } } ...
Review recursion
from CS-1112, Module-4
and
CS-1112, Module-9
In-Class Exercise 2.1:
Download QuickSortTest.java
and implement the QuickSort algorithm:
You will also need UniformRandom.java.
Algorithm: quickSort (data) Input: data - an array of size n 1. quickSortRecursive (data, 0, n-1) Method: quickSortRecursive (data, L, R) Input: data - an array of size n with a subrange specified by L and R 1. if L ≥ R 2. return 3. endif // Partition the array and position partition element correctly. 4. p = partition (data, L, R) 5. quickSortRecursive (data, L, p-1) // Sort left side 6. quickSortRecursive (data, p+1, R) // Sort right side
Let's look at a sample execution on a 10-character array "N G Q S K V S K W I"
N G Q S K V S K W I 0 1 2 3 4 5 6 7 8 9
Here is the trace (P denotes the index where the partition element ends up):
N G Q S K V S K W I Start K G I K N V S S W Q L=0 R=9 P=4 next intervals to recurse down: [0,3] [5,9] ^ ^ K G I K N V S S W Q L=0 R=3 P=3 next: [0,2] [4,3] ^ ^ I G K K N V S S W Q L=0 R=2 P=2 next: [0,1] [3,2] ^ ^ G I K K N V S S W Q L=0 R=1 P=1 next: [0,0] [2,1] ^ ^ G I K K N V S S W Q L=0 R=0 No partition (because L ≥ R) ^ G I K K N V S S W Q L=2 R=1 No partition ^ ^ G I K K N V S S W Q L=3 R=2 No partition ^ ^ G I K K N V S S W Q L=4 R=3 No partition ^ ^ G I K K N Q S S V W L=5 R=9 P=8 next: [5,7] [9,9] ^ ^ G I K K N Q S S V W L=5 R=7 P=5 next: [5,4] [6,7] ^ ^ G I K K N Q S S V W L=5 R=4 No partition ^ ^ G I K K N Q S S V W L=6 R=7 P=7 next: [6,6] [8,7] ^ ^ G I K K N Q S S V W L=6 R=6 No partition ^ G I K K N Q S S V W L=8 R=7 No partition ^ ^ G I K K N Q S S V W L=9 R=9 No partition ^
In-Class Exercise 2.2:
Next, we will examine some variations of QuickSort:
First, observe what happens with the following test data: "A B C D E F G H I J":
A B C D E F G H I J L=0 R=9 P=0 next: [0,-1] [1,9] ^ ^ A B C D E F G H I J L=0 R=-1 No partition ^ A B C D E F G H I J L=1 R=9 P=1 next: [1,0] [2,9] ^ ^ A B C D E F G H I J L=1 R=0 No partition ^ ^ A B C D E F G H I J L=2 R=9 P=2 next: [2,1] [3,9] ^ ^ A B C D E F G H I J L=2 R=1 No partition ^ ^ A B C D E F G H I J L=3 R=9 P=3 next: [3,2] [4,9] ^ ^ A B C D E F G H I J L=3 R=2 No partition ^ ^ A B C D E F G H I J L=4 R=9 P=4 next: [4,3] [5,9] ^ ^ A B C D E F G H I J L=4 R=3 No partition ^ ^ A B C D E F G H I J L=5 R=9 P=5 next: [5,4] [6,9] ^ ^ A B C D E F G H I J L=5 R=4 No partition ^ ^ A B C D E F G H I J L=6 R=9 P=6 next: [6,5] [7,9] ^ ^ A B C D E F G H I J L=6 R=5 No partition ^ ^ A B C D E F G H I J L=7 R=9 P=7 next: [7,6] [8,9] ^ ^ A B C D E F G H I J L=7 R=6 No partition ^ ^ A B C D E F G H I J L=8 R=9 P=8 next: [8,7] [9,9] ^ ^ A B C D E F G H I J L=8 R=7 No partition ^ ^ A B C D E F G H I J L=9 R=9 No partition ^
Why is this a bad thing?
Choosing a partitioning element:
A random partitioning element:
void quickSortRecursive (int[] data, int left, int right) { if (left < right) { // 1. Swap a random element into the leftmost position: int k = (int) UniformRandom.uniform ( (int) left+1, (int) right ); swap (data, k, left); // 2. Partition: int partitionPosition = leftPartition (data, left, right); // 3. Sort left side: quickSortRecursive (data, left, partitionPosition-1); // 4. Sort right side: quickSortRecursive (data, partitionPosition+1, right); } }Note:
Using the median approach:
int pickMedian3 (int[] data, int a, int b, int c) { // Return the index of the middle value among data[a], data[b] and data[c]. } void quickSortRecursive (int[] data, int left, int right) { if (left < right) { // 1. Pick the median amongst the left, middle and right elements // and swap into leftmost position: int k = pickMedian3 (data, left, (left+right)/2, right); swap (data, k, left); // 2. Partition: int partitionPosition = leftPartition (data, left, right); // 3. Sort left side: quickSortRecursive (data, left, partitionPosition-1); // 4. Sort right side: quickSortRecursive (data, partitionPosition+1, right); // Partition element is already in the correct place. } }
In-Class Exercise 2.3: Implement the median approach in your QuickSort algorithm.
Next, we will consider different partitioning ideas:
K G I L N V B E B Q LC=1 RC=9 Move RC left ^ ^ K G I L N V B E B Q LC=1 RC=8 Move LC right ^ ^ K G I L N V B E B Q LC=2 RC=8 Move LC right ^ ^ K G I L N V B E B Q LC=3 RC=8 Swap ^ ^ K G I B N V B E L Q LC=4 RC=7 Swap ^ ^ K G I B E V B N L Q LC=5 RC=6 Swap ^ ^ K G I B E B V N L Q LC=6 RC=5 Swap partition element ^ ^ B G I B E K V N L Q LC=6 RC=5 Partition complete ^ ^
Algorithm: leftBidirectionalPartition (data, left, right) Input: data array, indices left and right. // The partitioning element is the leftmost element: 1. partitionElement = data[left]; // Start bidirectional scan from next-to-left, and right end. 2. LC = left+1; RC = right; // Scan until cursors cross. 3. while (true) // As long as elements to the right are larger, skip over them: 4. while data[RC] > partitionElement RC = RC - 1; // As long as elements on the left are smaller, skip over them: 5. while (LC <= right) and (data[LC] < partitionElement) LC = LC + 1; // We've found the first pair to swap: 6. if (LC < RC) 7. Swap elements at positions LC and RC; 8. LC = LC + 1; RC = RC - 1; 9. else // Otherwise, if the cursors cross, we're done. 10. break out of loop; 11. endif 12. endwhile 13. Swap RC-th element with leftmost. 14. return RC; Output: new position of partition element
In-Class Exercise 2.4: In line 4 above, why don't we need to check whether RC becomes negative?
K G I L N V B E B Q LC=1 RC=1 Swap (in place) ^ K G I L N V B E B Q LC=2 RC=2 Swap (in place) ^ K G I L N V B E B Q LC=2 RC=3 ^ ^ K G I L N V B E B Q LC=2 RC=4 ^ ^ K G I L N V B E B Q LC=2 RC=5 ^ ^ K G I L N V B E B Q LC=2 RC=6 Swap ^ ^ K G I B N V L E B Q LC=3 RC=7 Swap ^ ^ K G I B E V L N B Q LC=4 RC=8 Swap ^ ^ K G I B E B L N V Q LC=5 RC=9 ^ ^ K G I B E B L N V Q LC=5 RC=9 Swap partition element ^ ^ B G I B E K L N V Q LC=5 RC=9 Partition complete ^ ^
Algorithm: leftUnidirectionalPartition (data, left, right) Input: data array, indices left and right. // The partitioning element is the leftmost element: 1. partitionElement = data[left]; // The trailing cursor for swaps: 2. LC = left; 3. // Loop over the forward cursor for examining data: 4. for RC=left+1 to right // Check whether an element is out-of-place: 5. if data[RC] < partitionElement // Switch behind trailing-cursor: 6. LC = LC + 1; 7. Swap elements at LC and RC; 8. endif 9. endfor 10. Swap LC element into leftmost; 11. return LC; Output: new position of partition element
Analysis of QuickSort:
Let's look at the details of the best-case analysis:
In-Class Exercise 2.5: Why is it true that the number of cuts is \(\log_{10/8} n\) above? And why is \(\log_{10/8} n = O(\log_e n)\)?
Next, here are some performance comparisons with Insertion-Sort (in seconds):
Data size | Insertion Sort | QuickSort-Randomized | QuickSort-Leftmost |
10000 | 0.9802 | 0.0044 | 0.008 |
30000 | 9.263 | 0.013 | 0.0276 |
50000 | 26.035 | 0.0225 | 0.0276 |
70000 | 51.172 | 0.0317 | 0.0495 |
90000 | 84.848 | 0.041 | 0.0715 |
Data size | Insertion Sort | QuickSort-Randomized | QuickSort-Leftmost |
1000 | 0 | 0.0020 | 0.009 |
1500 | 0.021 | 0.003 | 0.02 |
2000 | 0.038 | 0.004 | 0.034 |
2500 | 0.059 | 0.003 | 0.074 |
3000 | 0.084 | 0.004 | 0.098 |
Notice: the un-randomized quicksort performs poorly on
already-sorted data.
(The smaller sizes are used because Java complains about the
excessive recursion depth).
We will now look at some optimizations to QuickSort:
Three-way partitioning:
B A B C B B B D C A LC=1 RC=9 Move LC right ^ ^ B A B C B B B D C A LC=2 RC=9 Swap ^ ^ B A A C B B B D C B LC=3 RC=8 Move RC left ^ ^ B A A C B B B D C B LC=3 RC=7 Move RC left ^ ^ B A A C B B B D C B LC=3 RC=6 Swap ^ ^ B A A B B B C D C B LC=4 RC=5 Swap ^ ^ B A A B B B C D C B LC=5 RC=4 Swap partition element ^ ^Note: After the above partition, there will still be "B" to "B" comparisons.
B A B C B B B D C A LC=1 RC=9 Move LC right ^ ^ B A B C B B B D C A LC=2 RC=9 Swap ^ ^ B A A C B B B D C B LC=2 RC=9 Swap to right group ^ ^ B A A C B B B D C B LC=3 RC=8 Move RC left ^ ^ B A A C B B B D C B LC=3 RC=7 Move RC left ^ ^ B A A C B B B D C B LC=3 RC=6 Swap ^ ^ B A A B B B C D C B LC=3 RC=6 Swap to left group ^ ^ B B A A B B C D C B LC=4 RC=5 Swap ^ ^ B B A A B B C D C B LC=4 RC=5 Swap to left group ^ ^ B B B A A B C D C B LC=4 RC=5 Swap to right group ^ ^ B B B A A C C D B B LC=5 RC=4 Swap partition element ^ ^ A B B A B C C D B B LC=5 RC=4 Move left group to center ^ ^ A A B B B C C D B B LC=5 RC=4 Move left group to center ^ ^ A A B B B C C D B B LC=5 RC=4 Move right group to center ^ ^ A A B B B B C D B C LC=5 RC=4 Move right group to center ^ ^ A A B B B B B D C C LC=5 RC=4 ^ ^ A A B B B B B D C C Smaller left and right sub-arrays ___ _____Note:
void quickSortThreeWayRecursive (int[] data, int left, int right) { if (left < right) { // The partition returns both ends of the "partition-element range": PartitionRange pr = leftThreeWayPartition (data, left, right); // Sort (smaller) left side: quickSortThreeWayRecursive (data, left, pr.left-1); // Sort (smaller) right side: quickSortThreeWayRecursive (data, pr.right+1, right); } }
Removing some swaps:
void quickSortRecursive (int[] data, int left, int right) { if (left+1 == right) { // Handle this case right here. if (data[left] > data[right]) { // Swap: int temp = data[left]; data[left] = data[right]; data[right] = temp; } } else if (left < right) { // ... usual partition and recursion ... }
Mixed sorting:
In-Class Exercise 2.6: Assuming you have a complete InsertionSort, write pseudocode to use it in QuickSort, as described above.
Recall from QuickSort:
First, suppose two halves have been sorted. Here's how they're merged:
Sorted halves Additional merge space A A E F J C D G I K _ _ _ _ _ _ _ _ _ _ LC=0 RC=5 Merge from left into additional merge space ^ | ^ | A A E F J C D G I K A _ _ _ _ _ _ _ _ _ LC=1 RC=5 Merge from left | ^ | ^ | A A E F J C D G I K A A _ _ _ _ _ _ _ _ LC=2 RC=5 Merge from right | ^ | ^ | A A E F J C D G I K A A C _ _ _ _ _ _ _ LC=2 RC=6 Merge from right | ^ | ^ | A A E F J C D G I K A A C D _ _ _ _ _ _ LC=2 RC=7 Merge from left | ^ | ^ | A A E F J C D G I K A A C D E _ _ _ _ _ LC=3 RC=7 Merge from left | ^ | ^ | A A E F J C D G I K A A C D E F _ _ _ _ LC=4 RC=7 Merge from right | ^ ^ | A A E F J C D G I K A A C D E F G _ _ _ LC=4 RC=8 Merge from right | ^ ^ | A A E F J C D G I K A A C D E F G I _ _ LC=4 RC=9 Merge from left | ^ ^ A A E F J C D G I K A A C D E F G I J _ LC=5 RC=9 Merge only from right | | ^ ^ A A C D E F G I J K A A C D E F G I J K LC=5 RC=10 After merge | | ^ |
Using "merge" for sorting:
Algorithm: mergeSort (data) Input: data - an array of size n 1. mergeSortRecursive (data, 0, n-1) Algorithm: mergeSortRecursive (data, L, R) Input: data - an array of size n with a subrange specified by L and R 1. if L ≥ R // Bottom-out cases 2. return 3. else if L+1 = R 4. if data[R] < data[L] 5. swap (data, L, R) 6. endif 7. return 8. endif 9. M = (L + R) / 2 // Index of middle element. 10. mergeSortRecursive (data, L, M) // Sort the left half. 11. mergeSortRecursive (data, M+1, R) // Sort the right half. 12. merge (data, L, M, R)
The sorting code:
void mergeSortRecursive (Comparable[] data, int left, int right) { // 1. We will allow left cursor to go past right as one way // to bottom out of recursion. if (left >= right) return; // 2. Handle 2-element case directly: if (left+1 == right) { if (data[left].compareTo (data[right]) > 0) swap (data, left, right); return; } // 3. Find the middle point: int middle = (left+right) / 2; // 4. Sort the left side, including the middle: mergeSortRecursive (data, left, middle); // 5. Sort the right side: mergeSortRecursive (data, middle+1, right); // 6. Merge: simpleMerge (data, left, middle, right); }
Merging:
void simpleMerge (Comparable[] data, int left, int middle, int right) { // 1. Create the space and initialize the cursors: Comparable[] mergeSpace = new Comparable [right-left+1]; int leftCursor = left; int rightCursor = middle+1; // 2. Fill the merge space by one by one, selecting from the correct partition for (int i=0; i < mergeSpace.length; i++) { if (leftCursor > middle) { // 2.1 If left side is done, merge only from right: mergeSpace[i] = data[rightCursor]; rightCursor++; } else if (rightCursor > right) { // 2.2 If right side is done, merge only from left: mergeSpace[i] = data[leftCursor]; leftCursor++; } else if (data[leftCursor].compareTo (data[rightCursor]) <= 0) { // 2.3 Otherwise, if the leftCursor element is less, move it: mergeSpace[i] = data[leftCursor]; leftCursor++; } else { // 2.4 Move from right: mergeSpace[i] = data[rightCursor]; rightCursor++; } } // 3. Copy back into original array: for (int i=0; i < mergeSpace.length; i++) data[left+i] = mergeSpace[i]; }
Example: consider sorting this array of unit-sized strings: "F A A E J K I C G D" (only merge steps shown below):
Merge: A F A E J K I C G D _ _ _ LC=0 RC=2 Merge from left ^ | ^ A F A E J K I C G D A _ _ LC=1 RC=2 Merge from right | ^ ^ A F A E J K I C G D A A _ LC=1 RC=3 Merge only from left | ^ | ^ A A F E J K I C G D A A F LC=2 RC=3 After merge | | ^ ^ Merge: A A F E J K I C G D _ _ _ _ _ LC=0 RC=3 Merge from left ^ | ^ | A A F E J K I C G D A _ _ _ _ LC=1 RC=3 Merge from left | ^ | ^ | A A F E J K I C G D A A _ _ _ LC=2 RC=3 Merge from right | ^ ^ | A A F E J K I C G D A A E _ _ LC=2 RC=4 Merge from left | ^ ^ A A F E J K I C G D A A E F _ LC=3 RC=4 Merge only from right | | ^ ^ A A E F J K I C G D A A E F J LC=3 RC=5 After merge | | ^ | ^ Merge: A A E F J I K C G D _ _ _ LC=5 RC=7 Merge from right ^ | ^ A A E F J I K C G D C _ _ LC=5 RC=8 Merge only from left ^ | | ^ A A E F J I K C G D C I _ LC=6 RC=8 Merge only from left | ^ | ^ A A E F J C I K G D C I K LC=7 RC=8 After merge | | ^ ^ Merge: A A E F J C I K D G _ _ _ _ _ LC=5 RC=8 Merge from left ^ | ^ | A A E F J C I K D G C _ _ _ _ LC=6 RC=8 Merge from right | ^ | ^ | A A E F J C I K D G C D _ _ _ LC=6 RC=9 Merge from right | ^ | ^ A A E F J C I K D G C D G _ _ LC=6 RC=10 Merge only from left | ^ | | A A E F J C I K D G C D G I _ LC=7 RC=10 Merge only from left | ^ | A A E F J C D G I K C D G I K LC=8 RC=10 After merge | | ^ | Merge: A A E F J C D G I K _ _ _ _ _ _ _ _ _ _ LC=0 RC=5 Merge from left ^ | ^ | A A E F J C D G I K A _ _ _ _ _ _ _ _ _ LC=1 RC=5 Merge from left | ^ | ^ | A A E F J C D G I K A A _ _ _ _ _ _ _ _ LC=2 RC=5 Merge from right | ^ | ^ | A A E F J C D G I K A A C _ _ _ _ _ _ _ LC=2 RC=6 Merge from right | ^ | ^ | A A E F J C D G I K A A C D _ _ _ _ _ _ LC=2 RC=7 Merge from left | ^ | ^ | A A E F J C D G I K A A C D E _ _ _ _ _ LC=3 RC=7 Merge from left | ^ | ^ | A A E F J C D G I K A A C D E F _ _ _ _ LC=4 RC=7 Merge from right | ^ ^ | A A E F J C D G I K A A C D E F G _ _ _ LC=4 RC=8 Merge from right | ^ ^ | A A E F J C D G I K A A C D E F G I _ _ LC=4 RC=9 Merge from left | ^ ^ A A E F J C D G I K A A C D E F G I J _ LC=5 RC=9 Merge only from right | | ^ ^ A A C D E F G I J K A A C D E F G I J K LC=5 RC=10 After merge | | ^ |
Sometimes, it's easier to understand the execution of a recursive algorithm through a recursion tree:
In-Class Exercise 2.7:
Analysis:
Comparison with QuickSort:
In-Class Exercise 2.8: Why is MergeSort stable? Examine the code in simpleMerge() above and identify the critical line of code that makes it stable.
MergeSort variations:
void merge (int[] A, int leftA, int[] B, int leftB, int rightB, int[] C, int leftC, int rightC) { // Merge into A, starting from leftA, the ranges from B and C. } void mergeSortSwitchRecursive (int[] A, int[] B, int left, int right) { // 1. Bottom-out cases: // ... (not shown) // 2. Find the middle point: int middle = (left+right) / 2; // 3. Switch A and B in recursion so partitions are merged into B: mergeSortSwitchRecursive (B, A, left, middle); mergeSortSwitchRecursive (B, A, middle+1, right); // 4. Merge the two sorted partitions (in B) into A: merge (A, left, B, left, middle, B, middle+1, right); } public void mergeSortSwitch (int[] data) { // 1. Create the additional array: mergeSpace = new int [data.length]; // 2. Copy over data: for (int i=0; i < data.length; i++) mergeSpace[i] = data[i]; // 3. Final merge is into original data: mergeSortSwitchRecursive (data, mergeSpace, 0, data.length-1); }
F A A E J K I C G D Start A F A E J K I C G D MergeSize = 1 A F A E J K I C G D MergeSize = 1 A F A E J K I C G D MergeSize = 1 A F A E J K C I G D MergeSize = 1 A F A E J K C I D G MergeSize = 1 A A E F J K C I D G MergeSize = 2 A A E F C I J K D G MergeSize = 2 A A C E F I J K D G MergeSize = 4 A A C D E F G I J K MergeSize = 8 A A C D E F G I J K End
MergeSort optimizations:
First, some definitions:
Key ideas about binary heaps:
Insertion example:
Extracting min:
Implementing a binary min-heap:
In-Class Exercise 2.9:
Pseudocode:
Key methods: minHeapify, buildMinHeap, extractMin:
Algorithm: minHeapify (data, i) Input: heap, index into heap 1. left = left (i); 2. right = right (i); 3. smallest = i; 3. if (left < currentHeapSize) and (data[left] < data[i]) 4. smallest = left; 5. else if (right < currentHeapSize) and (data[right] < data[smallest]) 6. smallest = right; 7. endif 8. if (smallest != i) 9. swap (data, i, smallest); 10. minHeapify (data, smallest); 11. endif Output: subtree at i in min-heap-order
Algorithm: buildMinHeap (data) Input: data array 1. currentHeapSize = length of data; // Find the index from which the lowest level starts: 2. startOfLeaves = findStartOfLeaves (currentHeapSize); // Start heapifying from the rightmost parent. 3. for i=startOfLeaves-1 downto 0 4. minHeapify (data, i); 5. endfor Output: a min-order heap constructed from the data.
Algorithm: extractMin (data) Input: heap // Min element is in data[0]. 1. minValue = data[0]; // Swap last element into root: 2. data[0] = data[currentHeapSize-1]; // Reduce heap size: 3. currentHeapSize--; // Restore heap-order 4. minHeapify (data, 0); 5. return minValue; Output: minimum value
Sorting:
Example:
Building heap: heapifying data[6] = I F A A E J K I C G D P=6 L=13 R=14 Parent is smallest (=> no swap) ^ Building heap: heapifying data[5] = K F A A E J K I C G D P=5 L=11 R=12 Parent is smallest (=> no swap) ^ Building heap: heapifying data[4] = J F A A E J K I C G D P=4 L=9 R=10 Left is smallest (=> swap left with root) ^ ^ F A A E D K I C G J P=9 L=19 R=20 Parent is smallest (=> no swap) ^ Building heap: heapifying data[3] = E F A A E D K I C G J P=3 L=7 R=8 Left is smallest (=> swap left with root) ^ ^ ^ F A A C D K I E G J P=7 L=15 R=16 Parent is smallest (=> no swap) ^ Building heap: heapifying data[2] = A F A A C D K I E G J P=2 L=5 R=6 Parent is smallest (=> no swap) ^ ^ ^ Building heap: heapifying data[1] = A F A A C D K I E G J P=1 L=3 R=4 Parent is smallest (=> no swap) ^ ^ ^ Building heap: heapifying data[0] = F F A A C D K I E G J P=0 L=1 R=2 Left is smallest (=> swap left with root) ^ ^ ^ A F A C D K I E G J P=1 L=3 R=4 Left is smallest (=> swap left with root) ^ ^ ^ A C A F D K I E G J P=3 L=7 R=8 Left is smallest (=> swap left with root) ^ ^ ^ A C A E D K I F G J P=7 L=15 R=16 Parent is smallest (=> no swap) ^ Heap construction complete Extracting min: heapifying after extract min = A J C A E D K I F G _ P=0 L=1 R=2 Right is smallest (=> swap right with root) ^ ^ ^ A C J E D K I F G _ P=2 L=5 R=6 Right is smallest (=> swap right with root) ^ ^ ^ A C I E D K J F G _ P=6 L=13 R=14 Parent is smallest (=> no swap) ^ Extracting min: heapifying after extract min = A G C I E D K J F _ _ P=0 L=1 R=2 Left is smallest (=> swap left with root) ^ ^ ^ C G I E D K J F _ _ P=1 L=3 R=4 Right is smallest (=> swap right with root) ^ ^ ^ C D I E G K J F _ _ P=4 L=9 R=10 Parent is smallest (=> no swap) ^ Extracting min: heapifying after extract min = C F D I E G K J _ _ _ P=0 L=1 R=2 Left is smallest (=> swap left with root) ^ ^ ^ D F I E G K J _ _ _ P=1 L=3 R=4 Left is smallest (=> swap left with root) ^ ^ ^ D E I F G K J _ _ _ P=3 L=7 R=8 Parent is smallest (=> no swap) ^ Extracting min: heapifying after extract min = D J E I F G K _ _ _ _ P=0 L=1 R=2 Left is smallest (=> swap left with root) ^ ^ ^ E J I F G K _ _ _ _ P=1 L=3 R=4 Left is smallest (=> swap left with root) ^ ^ ^ E F I J G K _ _ _ _ P=3 L=7 R=8 Parent is smallest (=> no swap) ^ Extracting min: heapifying after extract min = E K F I J G _ _ _ _ _ P=0 L=1 R=2 Left is smallest (=> swap left with root) ^ ^ ^ F K I J G _ _ _ _ _ P=1 L=3 R=4 Right is smallest (=> swap right with root) ^ ^ ^ F G I J K _ _ _ _ _ P=4 L=9 R=10 Parent is smallest (=> no swap) ^ Extracting min: heapifying after extract min = F K G I J _ _ _ _ _ _ P=0 L=1 R=2 Left is smallest (=> swap left with root) ^ ^ ^ G K I J _ _ _ _ _ _ P=1 L=3 R=4 Left is smallest (=> swap left with root) ^ ^ G J I K _ _ _ _ _ _ P=3 L=7 R=8 Parent is smallest (=> no swap) ^ Extracting min: heapifying after extract min = G K J I _ _ _ _ _ _ _ P=0 L=1 R=2 Right is smallest (=> swap right with root) ^ ^ ^ I J K _ _ _ _ _ _ _ P=2 L=5 R=6 Parent is smallest (=> no swap) ^ Extracting min: heapifying after extract min = I K J _ _ _ _ _ _ _ _ P=0 L=1 R=2 Left is smallest (=> swap left with root) ^ ^ J K _ _ _ _ _ _ _ _ P=1 L=3 R=4 Parent is smallest (=> no swap) ^ Extracting min: heapifying after extract min = J K _ _ _ _ _ _ _ _ _ P=0 L=1 R=2 Parent is smallest (=> no swap) ^ Extracting min: heapifying after extract min = K _ _ _ _ _ _ _ _ _ _ P=0 L=1 R=2 Parent is smallest (=> no swap) Sorted data: A A C D E F G I J K
In-Class Exercise 2.10: Draw the heap at each step in the above example.
Analysis:
Sorting using a max-heap:
Maxheap example:
Building max heap: heapifying data[6] = I F A A E J K I C G D P=6 L=13 R=14 Parent is largest (=> no swap) ^ Building max heap: heapifying data[5] = K F A A E J K I C G D P=5 L=11 R=12 Parent is largest (=> no swap) ^ Building max heap: heapifying data[4] = J F A A E J K I C G D P=4 L=9 R=10 Parent is largest (=> no swap) ^ ^ Building max heap: heapifying data[3] = E F A A E J K I C G D P=3 L=7 R=8 Right is largest (=> swap right with root) ^ ^ ^ F A A G J K I C E D P=8 L=17 R=18 Parent is largest (=> no swap) ^ Building max heap: heapifying data[2] = A F A A G J K I C E D P=2 L=5 R=6 Left is largest (=> swap left with root) ^ ^ ^ F A K G J A I C E D P=5 L=11 R=12 Parent is largest (=> no swap) ^ Building max heap: heapifying data[1] = A F A K G J A I C E D P=1 L=3 R=4 Right is largest (=> swap right with root) ^ ^ ^ F J K G A A I C E D P=4 L=9 R=10 Left is largest (=> swap left with root) ^ ^ F J K G D A I C E A P=9 L=19 R=20 Parent is largest (=> no swap) ^ Building max heap: heapifying data[0] = F F J K G D A I C E A P=0 L=1 R=2 Right is largest (=> swap right with root) ^ ^ ^ K J F G D A I C E A P=2 L=5 R=6 Right is largest (=> swap right with root) ^ ^ ^ K J I G D A F C E A P=6 L=13 R=14 Parent is largest (=> no swap) ^ Heap construction complete Heapifying after swapping max = K A J I G D A F C E K P=0 L=1 R=2 Left is largest (=> swap left with root) ^ ^ ^ | J A I G D A F C E K P=1 L=3 R=4 Left is largest (=> swap left with root) ^ ^ ^ | J G I A D A F C E K P=3 L=7 R=8 Right is largest (=> swap right with root) ^ ^ ^ | J G I E D A F C A K P=8 L=17 R=18 Parent is largest (=> no swap) ^ | Heapifying after swapping max = J A G I E D A F C J K P=0 L=1 R=2 Right is largest (=> swap right with root) ^ ^ ^ | I G A E D A F C J K P=2 L=5 R=6 Right is largest (=> swap right with root) ^ ^ ^ | I G F E D A A C J K P=6 L=13 R=14 Parent is largest (=> no swap) ^ | Heapifying after swapping max = I C G F E D A A I J K P=0 L=1 R=2 Left is largest (=> swap left with root) ^ ^ ^ | G C F E D A A I J K P=1 L=3 R=4 Left is largest (=> swap left with root) ^ ^ ^ | G E F C D A A I J K P=3 L=7 R=8 Parent is largest (=> no swap) ^ ^ ^ Heapifying after swapping max = G A E F C D A G I J K P=0 L=1 R=2 Right is largest (=> swap right with root) ^ ^ ^ | F E A C D A G I J K P=2 L=5 R=6 Parent is largest (=> G is deleted) ^ ^ ^ Heapifying after swapping max = F A E A C D F G I J K P=0 L=1 R=2 Left is largest (=> swap left with root) ^ ^ ^ | E A A C D F G I J K P=1 L=3 R=4 Right is largest (=> swap right with root) ^ ^ ^ | E D A C A F G I J K P=4 L=9 R=10 Parent is largest (=> K is deleted) ^ | ^ Heapifying after swapping max = E A D A C E F G I J K P=0 L=1 R=2 Left is largest (=> swap left with root) ^ ^ ^ | D A A C E F G I J K P=1 L=3 R=4 Left is largest (=> swap left with root) ^ ^ ^ D C A A E F G I J K P=3 L=7 R=8 Parent is largest (=> no swap) ^ | ^ ^ Heapifying after swapping max = D A C A D E F G I J K P=0 L=1 R=2 Left is largest (=> swap left with root) ^ ^ ^ | C A A D E F G I J K P=1 L=3 R=4 Parent is largest (=> no swap) ^ ^ ^ Heapifying after swapping max = C A A C D E F G I J K P=0 L=1 R=2 Parent is largest (=> C is deleted) ^ ^ ^ Heapifying after swapping max = A A A C D E F G I J K P=0 L=1 R=2 Parent is largest (=> C is deleted) ^ ^ ^ Array is sorted
In-Class Exercise 2.11:
Apply the max-heap-sort to this data and show the array
contents:
What is a lower bound?
In-Class Exercise 2.12: Why is \(O(\log n!) = O(n\log n)\)?
In some applications, we can exploit knowledge about the data:
BucketSort for small-range integer data:
for i=0 to n count[data[i]] = count[data[i]] + 1; endfor
How does this square with the lower bound?
Note: