Recitation 8
Introduction
In lecture, we have talked about the runtimes of various sorting algorithms. This was also the first time we analyzed recursive algorithms' runtimes. In this recitation, you will review the runtime analysis of Quick Sort and Merge Sort, and have the chance to analyze the runtimes of other recursive algorithms.
Review
In recitation, the TA will walk through the runtime analysis of Merge Sort and Quick Sort. The algorithms for both are shown below. The algorithms come from TextMergeQuick.java (for Merge Sort) and Quick.java (for Quick Sort), but are modified to remove generics (to make the code easier to read).
Merge Sort
public static void mergeSort(Integer[] a, int first, int last)
{
Integer[] tempArray = new Integer[a.length];
mergeSort(a, tempArray, first, last);
}
private static void mergeSort(Integer[] a, Integer[] tempArray, int first, int last)
{
if (first < last)
{
// sort each half
int mid = (first + last)/2;// index of midpoint
mergeSort(a, tempArray, first, mid); // sort left half array[first..mid]
mergeSort(a, tempArray, mid + 1, last); // sort right half array[mid+1..last]
merge(a, tempArray, first, mid, last); // merge the two halves
}
}
private static void merge(Integer[] a, Integer[] tempArray, int first, int mid, int last)
{
// Two adjacent subarrays are a[beginHalf1..endHalf1] and a[beginHalf2..endHalf2].
int beginHalf1 = first;
int endHalf1 = mid;
int beginHalf2 = mid + 1;
int endHalf2 = last;
// while both subarrays are not empty, copy the smaller item into the temporary array
int index = beginHalf1; // next available location in tempArray
for (; (beginHalf1 <= endHalf1) && (beginHalf2 <= endHalf2); index++)
{
// Invariant: tempArray[beginHalf1..index-1] is in order
if (a[beginHalf1].compareTo(a[beginHalf2]) < 0)
{
tempArray[index] = a[beginHalf1];
beginHalf1++;
}
else
{
tempArray[index] = a[beginHalf2];
beginHalf2++;
}
}
// finish off the nonempty subarray
// finish off the first subarray, if necessary
for (; beginHalf1 <= endHalf1; beginHalf1++, index++)
{
// Invariant: tempArray[beginHalf1..index-1] is in order
tempArray[index] = a[beginHalf1];
}
// finish off the second subarray, if necessary
for (; beginHalf2 <= endHalf2; beginHalf2++, index++)
{
// Invariant: tempa[beginHalf1..index-1] is in order
tempArray[index] = a[beginHalf2];
}
// copy the result back into the original array
for (index = first; index <= last; index++)
{
a[index] = tempArray[index];
}
}
Quick Sort
public static void quickSort(Integer[] array, int first, int last)
{
if (first < last)
{
// create the partition: Smaller | Pivot | Larger
int pivotIndex = partition(array, first, last);
// sort subarrays Smaller and Larger
quickSort(array, first, pivotIndex-1);
quickSort(array, pivotIndex+1, last);
}
}
private static int partition(Integer[] a, int first, int last)
{
int pivotIndex = last;
T pivot = a[pivotIndex];
// determine subarrays Smaller = a[first .. endSmaller]
// and Larger = a[endSmaller+1 .. last-1]
// such that elements in Smaller are <= pivot and
// elements in Larger are >= pivot; initially, these subarrays are empty
int indexFromLeft = first;
int indexFromRight = last - 1;
boolean done = false;
while (!done)
{
// starting at beginning of array, leave elements that are < pivot;
// locate first element that is >= pivot
while (a[indexFromLeft].compareTo(pivot) < 0)
{
indexFromLeft++;
}
// starting at end of array, leave elements that are > pivot;
// locate first element that is <= pivot
while (a[indexFromRight].compareTo(pivot) > 0 && indexFromRight > first)
{
indexFromRight--;
}
// Assertion: a[indexFromLeft] >= pivot and
// a[indexFromRight] <= pivot.
if (indexFromLeft < indexFromRight)
{
swap(a, indexFromLeft, indexFromRight);
indexFromLeft++;
indexFromRight--;
}
else
{
done = true;
}
}
// place pivot between Smaller and Larger subarrays
swap(a, pivotIndex, indexFromLeft);
pivotIndex = indexFromLeft;
// Assertion:
// Smaller = a[first..pivotIndex-1]
// Pivot = a[pivotIndex]
// Larger = a[pivotIndex + 1..last]
return pivotIndex;
}
private static void swap(Integer [] a, int i, int j)
{
Integer temp = a[i];
a[i] = a[j];
a[j] = temp;
}
Practice Problems and Questions
Recursive Algorithms
Now that you have reviewed in detail how to analyze recursive algorithms, try determining the runtimes of these algorithms:
- Recursive Factorial:
public long factorial (int N)
{
if (N < 0)
throw new IllegalArgumentException();
if (N <= 1)
return 1;
return N * factorial(N-1);
}
- Recursive Sequential Search (non-generic form of SeqSArray.java, see also a Linked List version):
public static int recSeqSearch(Integer [] a, Integer key, int first)
{
if (first >= a.length) // base case not found
return -1;
else if (a[first].compareTo(key) == 0) // base case found
return first;
else return recSeqSearch(a, key, first+1); // recursive case
}
- Memoized Integer Exponentiation
public long power(long X, long N)
{
if (N == 0) //base case
return 1;
else if (N == 1) //base case
return X;
else //recursive case
{
if (N % 2 == 0)
{
long result = power(X, N/2); //memoize
return result * result;
}
else
{
long result = power(X, N/2); //memoize
return result * result * X;
}
}
}
Additional Questions
- The code for Merge Sort and Quick Sort above no longer uses generics. How would the analysis change if you were analysizing generic code instead?
- The recursive sequential search algorithm above has O(N). We saw earlier in the semester that the iterative sequential search also has O(N). So, does it matter which version you use in your programs? Why?
Final Note
Many recursive algorithms, especially divide and conquer algorithms, can be solved using Recurrence Relations. In fact, this is how you would need to analyze the non-memoized integer exponentiation algorithm. The Master Theorem makes solving many recurrence relations very quick and relatively easy. These are topics that will be covered in CS/COE 1501 (Algorithm Implementation), but it might be helpful to start learning them before CS/COE 1501.
Submission and Grading
This is for your practice. The final runtimes are provided for you so you can check your answer. If you have any questions about your analysis or your answers to the additional questions, please talk to the instructor or the TA.
Answers for Recursive Runtimes
- Recursive Factorial: O(N)
- Recursive Sequential Search: O(N)
- Memoized Integer Exponentiation: O(log(N))