QuickSort
Problem and Approach
- Want to sort a list of numbers
- Divide and conquer (like MergeSort)
- Put "small" numbers at beginning, "large" numbers at end
- Then sort each half
- Recursive algorithm plus "helper" partition algorithm
- Base case: only one element in list; is sorted
The Algorithm(s)
void QuickSort( float array[], int start_pos, int end_pos)
{
if ( start_pos == end_pos ) // Only one element
return;
int middle_pos = Partition( array, start_pos, end_pos); // Reposition elements
QuickSort( array, start_pos, middle_pos); // Sort left half
QuickSort( array, middle_pos + 1, end_pos); // Sort right half
}
int Partition( float array[], int start_pos, int end_pos)
{
float pivot = array[start_pos]; // Smaller than pivot on left; larger on right
int left_index = start_pos; // First element
int right_index = end_pos; // Last element
while ( 1 ) // Loop forever; return once partitioning is completed
{
// Skip over large elements on right
while ( array[right_index] > pivot && right_index >= start_pos )
right_index - -;
// Skip over small elements on left
while ( array[left_index] < pivot && left_index <= end_pos )
left_index ++;
if ( left_index < right_index ) // Exchange if halves aren't complete
{
float temp = array[left_index];
array[left_index] = array[right_index];
array[right_index] = temp;
left_index ++ ; // Skip over exchanged values
right_index -- ;
}
else // Otherwise, return location of pivot
return right_index;
}
}
initial call: Quicksort ( A, 0, n-1 );
Why does this work?
Correctness of QuickSort
- Correct on one element -- returns with no further sorting
- More than one element -- put correct numbers into each half, then sort each half (closer to base case)
- Assuming Partition works correctly, array will be sorted
- We are passing some of the work of sorting (selection of correct elements) onto Partition
Correctness of Partition
- Pivot around first element in array (under consideration)
- All larger should be on right; skip those that are already there
- All smaller should be on left; skip those that are already there
- If a wrong placement (small on right, large on left), swap them
- Continue in this fashion until left, right sides correctly placed
- Just return point of division; not necessarily half on each side
Runtime Analysis
- Must analyze both Partition and QuickSort
- Partition:
several O(1) initial operations
main while loop: perform several O(1) operations each iteration
at most n iterations, since one array element skipped each time
so, O(n) for this loop
Total Running Time: O(1) + O(n) = O(n), on an n-long input array
- QuickSort:
call Partition: O(array size)
make 2 recursive calls: O(1) (count runtime of recursive calls separately)
top-level call: O(1) + O(n) = O(n)
2 second-level calls: O(1) + O(k), O(1) + O(n-k), so O(n) total
4 third-level calls: O(1) + O(i) = O(i) each (sum of i's is n), so O(n) total -- etc.
Each level of call takes O(n); how many levels?
How many times can we split up an n-long array?
lg(n) if split exactly in half each time
n if only split off one element each time
Best Case: lg(n) levels * O(n) time each = O(n lg n)
Worst Case: n levels * O(n) time each = O(n2)
Total Running Time: O(n lg n), on an n-long input array
A Word about Efficiency
- O(n lg n) is optimal sort time
- To try to guarantee that performance, can use a randomized partition
- This picks a random element to partition around (instead of first)
- Now no malicious user can structure input so as to cause worst performance