10b: Sorting
ICS211, Fall 2012
Dr. Zach
(switch view)
Status check
- Exam 2 ... sorry, not yet. :(
- grades to Laulima as soon as they're done though
- A07 - faqs
- A08
Last time
- Insertion sort, Selection sort
- both fairly useful: O(n^2)
- Other approaches:
- What's the simplest sort we could write?
- What sorts are more efficient?
Bubble sort
public static <T extends Comparable<T>> void bubbleSort(T[] array) {
boolean swapped;
do {
swapped = false;
for (int i = 0; i < array.length - 1; i++) {
if (array[i].compareTo(array[i+1]) > 0) {
//swap the two
E temp = array[i];
array[i] = array[i + 1];
array[i + 1] = temp;
swapped = true;
}
}
}while (swapped);
}
- Trace: [5, 3, 1, 4]
- show i, swapped, and array contents every time you reach the end of the for loop
Bubble sort performance
- How would it perform (big-O) on:
- Already sorted?
- Reverse sorted?
- Average case?
- stable?
- How could we improve it? track the last swap made on trip through list and only go that far next time
- (doesn't change big-O, but improves practical overhead)
Merge sort
- Idea: lets use recursion to break the problem in half
- Base case: a single element is already sorted
- Tricky part: merging the sorted sub problems on the way back up
- (example)
Merge sort code (1/3)
public static <T extends Comparable<T>> void mergeSort(T[] array) {
@SuppressWarnings("unchecked")
T[] aux = (T[]) new Comparable[array.length];
mergeSort(array, aux, 0, array.length - 1);
}
Merge sort code (2/3)
private static <E extends Comparable<E>> void mergeSort(T[] array, T[] aux, int left, int right) {
if(left < right) {
int mid = (left + right) / 2;
mergeSort(array, aux, left, mid);
mergeSort(array, aux, mid + 1, right);
merge(array, aux, left, mid, right);
}
}
Merge sort code (3/3)
private static <T extends Comparable<T>> void merge(T[] array, T[] aux,
int startLeft, int mid, int endRight) {
int endLeft = mid;
int startRight = mid + 1;
//cursor indexes
int leftPos = startLeft;
int rightPos = startRight;
int auxPos = startLeft;
//merge from left or right, whichever is smaller, into aux
while (leftPos <= endLeft && rightPos <= endRight) {
if (array[leftPos].compareTo(array[rightPos]) <= 0) {
aux[auxPos++] = array[leftPos++];
}else {
aux[auxPos++] = array[rightPos++];
}
}
//at this point, all of left or all of right copied over
//so copy the remainder of other
while(leftPos <= endLeft) {
aux[auxPos++] = array[leftPos++];
}
while(rightPos <= endRight) {
aux[auxPos++] = array[rightPos++];
}
//now copy sorted aux section back into original array
for(int i = startLeft; i <= endRight; i++)
array[i] = aux[i];
}
}
//based partly on the implementation from http://www.java-tips.org/java-se-tips/java.lang/merge-sort-implementation-in-java.html
Merge sort analysis
- Big-O? O(n lg n)
- How do we compute this based on the tree-like structure of sub-problem recursive calls?
- Best case or worst case? No. Always O(n lg n)
- Stable? yes, if implemented correctly
- So what's the drawback? O(n) extra space: the auxillary array
- There are in-place versions, though slower in practice
- Even normal version slow in practice: lots of copying overhead here
- Other versions:
- Optimized merge: if already in correct order (used by Java 6 and earlier)
- Bottom-up iterative version
- On linked lists (actually better than for arrays)
Quicksort
- Named so because fast in practice
- usually O(n lg n) with lower constant overhead than mergesort
- Interestingly, more comparisons usually are made than mergesort
- And O(n2) worst case
- Not usually stable (though can be made to be)
- But no extra array needed, nor all of that extra copying back and forth
- Idea:
- Divide in half and recurse, like merge sort
- But sort on the way down based on a central pivot
- (examples)
Quicksort code (for reference)
public static <T extends Comparable<? super T>> void quickSort(T[] array) {
quickSort(array, 0, array.length - 1);
}
private static <T extends Comparable<? super T>> void quickSort(T[] array,
int start, int end) {
if (start < end) {
int pivot = partition(array, start, end);
quickSort(array, start, pivot - 1);
quickSort(array, pivot + 1, end);
}
}
/**
* Picks a random pivot value from the given range and then
* sorts the remaining values into two groups: those larger
* and those smaller than the pivot. Places the pivot value
* between these groups and returns its index.
*/
private static <T extends Comparable<? super T>> int partition(T[] array,
int start, int end) {
//find pivot and swap into end position
int pivot = (int) (Math.random() * (end - start + 1)) + start;
T temp = array[pivot];
array[pivot] = array[end];
array[end] = temp;
pivot = end;
int less = start - 1;
for (int greater = start; greater < end; greater++) {
if (array[greater].compareTo(array[pivot]) < 0) {
//actually expand lesser group instead
less++;
temp = array[less];
array[less] = array[greater];
array[greater] = temp;
}
//else: just leave where it is in the greater range
}
//swap pivot into end of lesser group
less++; //actually first of greater set
temp = array[less];
array[less] = array[pivot];
array[pivot] = temp;
pivot = less;
return pivot;
}
Quicksort analysis
- Not always divided in half each time
- Pivot selection is vital
- Easiest: just pick last each time so no swap needed
- But then already sorted is worst case!
- Almost as easy: random pick
- Pick 3 and then take median
- Big-O: usually O(n lg n), can be O(n2)
For next time...
- Trees
- A07, A08 (start)
- Quiz to be posted...