3.3. Divide & Conquer Sort

Merge sort, quick sort, intro sort

Divide & Conquer

  • Divide the problem into sub-problems (recursively).

  • Conquer sub-problems, which effectively solve the super problem.

Complexity

MergeSort

TimSort

QuickSort

IntroSort

Best

O(nlogn)O(n \log n)

O(nlogn)O(n \log n)

O(nlogn)O(n \log n)

O(nlogn)O(n \log n)

Worst

O(nlogn)O(n \log n)

O(nlogn)O(n\log n)

O(n2)O(n^2)

O(nlogn)O(n \log n)

Average

O(nlogn)O(n \log n)

O(nlogn)O(n \log n)

O(nlogn)O(n \log n)

O(nlogn)O(n \log n)

Why do people ever want to use QuickSort?

Merge Sort

  • Divide the input array into two sub-arrays.

  • Sort each of the sub-arrays and merge them into the back.

Let us create the MergeSort class inheriting AbstractSort:

public class MergeSort<T extends Comparable<T>> extends AbstractSort<T> {
    private T[] temp;

    public MergeSort() {
        this(Comparator.naturalOrder());
    }

    public MergeSort(Comparator<T> comparator) {
        super(comparator);
    }
}
  • L2: holds the copy of the input array.

Let us then override the sort() method that calls the helper method:

  • L4-5: increases the size of the temp array if necessary.

    • L5: unchecked type Object to T[].

What is the advantage of declaring the member field temp?

The helper method can be defined as follows:

  • L11: sorts the left sub-array.

  • L12: sorts the right sub-array.

  • L13: merges the left and right sub-arrays.

Finally, the merge() method can be defined as follows:

  • L10-11: copies the input array to the temporary array and counts the assignments.

  • L14-15: no key left in the 1st half.

  • L16-17: no key left in the 2nd half.

  • L18-19: the 2nd key is greater than the 1st key.

  • L20-21: the 1st key is greater than or equal to the 2nd key.

How many assignments are made for the nn number of keys by the above version of MergeSort?

Quick Sort

  • Pick a pivot key in the input array.

  • Exchange keys between the left and right partitions such that all keys in the left and right partitions are smaller or bigger than the pivot key, respectively.

  • Repeat this procedure in each partition, recursively.

Let us create the QuickSort class inheriting AbstractSort:

Let us then override the sort() method:

  • L3: stops when the pointers are crossed.

  • L6: sorts the left partition.

  • L7: sorts the right partition.

The partition() method can be defined as follows:

  • L5: finds the left pointer where endIndex > fst > pivot.

  • L6: finds the right pointer where beginIndex < snd < pivot.

  • L7: the left and right pointers are crossed.

  • L8: swaps between keys in the left and right partitions.

  • L11: swaps the keys in the beginIndex and pivot.

The programming design in L5 and L6 are not ideal since the while loops do not include anybody that can confuse other programmers.

Intro Sort

  • Although Quick Sort is the fastest on average, the worst-case complexity is O(n2)O(n^2).

  • \exists sorting algorithms that guarantee faster worst-case complexities than Quick Sort: \Rightarrow Quick Sort for random cases and a different algorithm for the worst case.

How can we determine if Quick Sort is meeting the worse-case?

Let us define the IntroSoft class inheriting QuickSort:

  • L2: declares a sorting algorithm to handle the worst cases.

  • L14: resets the counters for both the main and auxiliary sorting algorithms.

We then override the sort() method by passing the maximum depth to the auxiliary method:

  • L9: returns 2logn2 \log n as the maximum depth.

Finally, the auxiliary method sort() can be defined as follows:

  • L4-5: switches to the other sorting algorithm if the depth of the partitioning exceeds the max depth.

Does the max-depth need to be set to 2logn2 \log n?

Benchmarks

The following shows runtime speeds, assignment costs, and comparison costs between several sorting algorithms for the random, best, and worst cases.

https://www.slideshare.net/jchoi7s/dsajava-divide-conquer-sorting-algorithms-benchmarks

Last updated

Was this helpful?