I was implementing a merge sort in Algorithms in Java 4th edition.
My basic merge sort works, and I want to improve the algorithm by using insertion sort when the array size is less than 7.
I thought it is obvious an efficient improvement, but actually the original one is faster than the improved one for large data.
Here is my improved merge sort, CUTOFF = 7:
private static void merge(Comparable[] a, Comparable[] aux, int lo, int mid, int hi) {
// Copy to aux[]
for (int i = lo; i <= hi; i++) {
aux[i] = a[i];
}
// Merge back to a[]
int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++) {
if (i > mid) a[k] = aux[j++];
else if (j > hi) a[k] = aux[i++];
else if (less(aux[i], aux[j])) a[k] = aux[i++];
else a[k] = aux[j++];
}
}
private static void sort(Comparable[] a, Comparable[] aux, int lo, int hi) {
// #1 improvement
// Stop condition for this recursion.
// This time we add a CUTOFF, when the items in array
// is less than 7, we will use insertion sort.
if (hi <= lo + CUTOFF - 1) {
Insertion.sort(a, lo, hi);
return;
}
int mid = lo + (hi - lo) / 2;
sort(a, aux, lo, mid);
sort(a, aux, mid + 1, hi);
if (!less(a[mid+1], a[mid])) return;
merge(a, aux, lo, mid, hi);
}
public static void sort(Comparable[] a) {
Comparable[] aux = new Comparable[a.length];
sort(a, aux, 0, a.length - 1);
}
The insertion sort code:
public static void sort(Comparable[] a, int lo, int hi) {
for (int i = lo; i <= hi; i++) {
for (int j = i; j > 0 && less(a[j], a[j - 1]); j--) {
exch(a, j, j - 1);
}
}
}
I used a SortCompare.java to compare the execute time:
public class SortCompare {
public static double time(String alg, Comparable[] a) {
Stopwatch timer = new Stopwatch();
if (alg.equals("Insertion")) Insertion.sort(a);
if (alg.equals("Selection")) Selection.sort(a);
if (alg.equals("Shell")) Shell.sort(a);
if (alg.equals("Merge")) Merge.sort(a);
if (alg.equals("MergeWithImprovements")) MergeWithImprovements.sort(a);
//if (alg.equals("Quick")) Quick.sort(a);
//if (alg.equals("Heap")) Heap.sort(a);
if (alg.equals("InsertionWithSentinel")) InsertionWithSentinel.sort(a);
return timer.elapsedTime();
}
public static double timeRandomInput(String alg, int N, int T) {
// Use alg to sort T random arrays of length N.
double total = 0.0;
Double[] a = new Double[N];
for (int t = 0; t < T; t++) {
for (int i = 0; i < N; i++) {
a[i] = StdRandom.uniform();
}
total += time(alg, a);
}
return total;
}
public static void main(String[] args) {
String alg1 = args[0];
String alg2 = args[1];
int N = Integer.parseInt(args[2]);
int T = Integer.parseInt(args[3]);
double t1 = timeRandomInput(alg1, N, T); // Total for alg1
double t2 = timeRandomInput(alg2, N, T);
StdOut.printf("For %d random Doubles\n %s is", N, alg1);
StdOut.printf(" %.1f times faster than %s\n", t2/t1, alg2);
}
}
I generated 100 arrays with 10000 elements each. The original merge sort is 30 times faster than the improved one!
You insertion sort function is definitely wrong. Note the j > 0 end condition. You pass in [lo..hi] but your code can iterate j all the way down to 1. I think you want something like:
public static void sort(Comparable[] a, int lo, int hi) {
for (int i = lo + 1; i <= hi; i++) {
for (int j = i; j > lo && less(a[j], a[j - 1]); j--) {
exch(a, j, j - 1);
}
}
}
Related
I've implemented a QuickSort algorithm along with a Time-Complexity control. It works fine with smaller N but once i get closer to larger N the StackOverflow is inevitable. Im thinking that having the pivot element as the last element might be what's causing this.
My first thought was to simply always use the middle element as the pivot element to avoid this but since the test-program throws an 'unsorted exception', it's not a valid solution.
Any ideas how i can work my way around this?
public class QuickSorter implements IntSorter{
int partition (int a[], int lo, int hi) {
int pivot = a[hi]; // pivot element
int i = (lo - 1);
for (int j = lo; j <= hi - 1; j++) {
if (a[j] < pivot) {
i++;
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}
int temp = a[i+1];
a[i+1] = a[hi];
a[hi] = temp;
return (i + 1);
}
#Override
public void sort(int[] a) {
int lo = 0;
int hi = a.length-1;
if (lo < hi) {
int p = partition(a, lo, hi);
sort(a, lo, p - 1);
sort(a, p + 1, hi);
}
}
private void sort(int[] a, int lo, int hi) {
if (lo < hi) {
int p = partition(a, lo, hi);
sort(a, lo, p - 1);
sort(a, p + 1, hi);
}
}
}
Testcode:
private static void testSort(IntSorter sorter, int firstN, boolean ordered) {
double t1 = 0;
int N = firstN/2;
while (t1 < 0.7 && N < 10000000) {
N *= 2;
int[] a = create(N, ordered);
t1 = timeit(sorter, a);
System.out.println("T("+N+")="+t1);
ArrayUtil.testOrdered(a);
}
int[] a = create(4*N, ordered);
double t4 = timeit(sorter, a);
ArrayUtil.testOrdered(a);
double t01 = t1 / (N * Math.log(N ));
double t04 = t4 / (4*N * Math.log(4*N));
System.out.println("T("+4*N+")="+t4+" growth per N log N: "+t04/t01);
if (t04/t01 > 1.25) {
System.out.println(sorter.getClass().getName()+".sort appears not to run in O(N log N) time");
System.exit(1);
}
}
public static void testOrdered(int[] a) {
int N = a.length;
for (int i = 1; i < N; i++) {
if (a[i] < a[i-1]) {
throw new SortingException("Not sorted, a["+(i-1)+"] > a["+i+"]");
}
}
}
As Thomas comments, using the middle element as the pivot should work fine. It's a common choice, actually, because it works well with input arrays that happen to be already fully or partially sorted.
As for avoiding stack overflow, a common approach is to only recurse on the shorter part after a partitioning step - this ensures at least a halving of the array being processed at each level, so e.g. a 1,000,000 element array will have a maximum call depth of roughly 20 ( log2(1,000,000) ).
So, instead of
private void sort(int[] a, int lo, int hi) {
if (lo < hi) {
int p = partition(a, lo, hi);
sort(a, lo, p - 1);
sort(a, p + 1, hi);
}
}
You do
private void sort(int[] a, int lo, int hi) {
while (lo < hi) {
int p = partition(a, lo, hi);
// recurse on smaller part, loop on larger part
if (((p - 1) - lo) > (hi - (p + 1))) {
sort(a, p + 1, hi);
hi = p - 1;
}
else {
sort(a, lo, p - 1);
lo = p + 1;
}
}
}
Shuffle the array before sorting with the method below seems to have fixed the issues i had aswell
public void shuffle(int[] a) {
int N = a.length;
Random randomGenerator = new Random();
for (int i = 0; i < N; i++) {
int r = i + randomGenerator.nextInt(N-i); // between i and N-1
int t = a[i]; a[i] = a[r]; a[r] = t;
}
}
I am (for homework, if that influences how the answers are given) supposed to be writing a class for quicksort to sort an array from a txt or dat file provided by the user. I present the user with an menu option in the driver class to select sort type and then pass the array to the quicksort class for sorting, after which it is returned back to the driver class to be written to a file. I believe the return is operating correctly, as I get the unsorted array printed to the file, however it only appears to be making one pass through the quicksort loop (determined by only one printing of the "Last Loop" string to the console.
The addition bits of code are part of the assignment, allowing a user to select Insertion sort to complete the sort if only 50 or 100 items remain in the unsorted array based on user choice. I have tried running this program commenting those two if statements out and still the program does not correctly sort. I am certain the input is read correctly as I have an addition HeapSort class that does sort correctly, and insertion sort returns correctly if that option is selected and the file size is less than 50 or 100 ints. I cannot seem to get the QuickSort of a larger file to trigger though.
class Quicksort{
int partition(int arr[], int lb, int ub){
int i = lb, j = ub;
int temp;
int pivot = arr[0];
while (i <= j) {
while (arr[i] < pivot){
i++;
}while(arr[j] > pivot){
j--;
}if (i <= j){
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
i++;
j--;
}
}return i;
}
int[] quicksort(int arr[], int lb, int ub, boolean insertLarge, boolean insertSmall){
if (insertLarge && arr.length <= 100){
System.out.println("Large");
insertionSort(arr);
}
else if (insertSmall && arr.length <= 50){
System.out.println("Small");
insertionSort(arr);
}
else{
int[] intArrCopy = new int[arr.length-1];
for (int z = 1; z < intArrCopy.length; z++){
intArrCopy[z-1] = arr[z];
}
System.out.println("Last Loop");
int index = partition(arr, lb, ub);
if (lb < index-1){
quicksort(intArrCopy, lb, index-1, insertLarge, insertSmall);
}if (index < ub){
quicksort(intArrCopy, index, ub, insertLarge, insertSmall);
}
}return arr;
}
public static void insertionSort(int x[]){
int h, k, y;
for (k=1; k < x.length; k++){
y = x[k];
for (h = k-1; h>=0 && y < x[h]; h--){
x[h+1] = x[h];
}x[h+1] = y;
}
}
}
And how it is being called from driver class:
private static void processChoice(int choice, int[] intArr){
switch (choice){
case 1:
p = intArr.length;
output = new int[p];
startTime = System.nanoTime();
output = quick.quicksort(intArr, intArr[0], intArr[p-1], insertLarge, insertSmall);
endTime = System.nanoTime();
System.out.println("Time Taken: "+(endTime-startTime)+" nanoseconds.");
break;
}
}
Assuming a hybrid sort, try something like this:
void quicksort(int arr[], int lb, int ub){
if ((ub-lb) < 100){ // < 32 may be fastest
System.out.println("Large");
insertionSort(arr, lb, ub);
return;
}
int index = partition(arr, lb, ub);
if (lb < index-1)
quicksort(arr, lb, index-1);
if (index < ub)
quicksort(arr, index, ub);
}
public static void insertionSort(int x[], int lb, int ub){
int h, k, y;
for (h = lb+1; h <= ub; h++){
y = x[h];
for (k = h; k > lb && x[k-1] > y; k--)
x[k] = x[k-1];
x[k] = y;
}
}
Sorry, beginner here.This is what I have right now:
public class MergeSort
{
public static void main(String[] args)
{
int[] arr = {3, 5, 2, 4, 1};
sort(arr, 0, arr.length - 1);
for(int i = 0; i < arr.length; i++)
{
System.out.print(arr[i] + " ");
}
}
private static void sort(int[] arr, int lo, int hi)
{
if(lo >= hi)
{
return;
}
int mid = (lo + hi)/2;
sort(arr, lo, mid);
sort(arr, mid + 1, hi);
int size = hi - lo + 1;
int[] temp = new int[size]; //new array to merge into
merge(arr, temp, lo, mid + 1, hi);
for(int i = 0; i < size; i++)
{
arr[i + lo] = temp[i];
}
}
private static void merge(int[] arr, int[] temp, int lower, int mid, int upper)
{
int tempIndex = 0;
int leftLo = lower;
int leftHi = mid - 1;
int rightLo = mid;
int rightHi = upper;
while(leftLo <= leftHi && rightLo <= rightHi)
{
if(arr[leftLo] < arr[rightLo])
{
temp[tempIndex] = arr[leftLo];
tempIndex++;
leftLo++;
}
else
{
temp[tempIndex] = arr[rightLo];
tempIndex++;
rightLo++;
}
}
}
}
I know it's the merge function that is not working, because right now it prints out only the smallest element and the rest as 0's. I think it has something to do with needing another while loop to copy the array, but I don't know how to write that, or even the purpose of it, as right now it seems that the array is being merged into the temp array in a correct order. Why is it only printing the first element correctly? Thanks.
In merge, you copy values as long as leftLo and rightLo both haven't reached their limit yet. Typically one of them reaches early. Then you need to copy the remaining values of the other one. You can copy the remaining elements by adding these two loops:
while (leftLo <= leftHi) {
temp[tempIndex] = arr[leftLo];
tempIndex++;
leftLo++;
}
while (rightLo <= rightHi) {
temp[tempIndex] = arr[rightLo];
tempIndex++;
rightLo++;
}
That is, the complete method becomes:
private static void merge(int[] arr, int[] temp, int lower, int mid, int upper) {
int tempIndex = 0;
int leftLo = lower;
int leftHi = mid - 1;
int rightLo = mid;
int rightHi = upper;
while (leftLo <= leftHi && rightLo <= rightHi) {
if (arr[leftLo] < arr[rightLo]) {
temp[tempIndex] = arr[leftLo];
tempIndex++;
leftLo++;
} else {
temp[tempIndex] = arr[rightLo];
tempIndex++;
rightLo++;
}
}
while (leftLo <= leftHi) {
temp[tempIndex] = arr[leftLo];
tempIndex++;
leftLo++;
}
while (rightLo <= rightHi) {
temp[tempIndex] = arr[rightLo];
tempIndex++;
rightLo++;
}
}
I was trying to sort the index of an array using mergesort. The mergesort works perfectly , and the final answer is exactly correct but I am not sure why the indexes do not work out in correct positions. I do not want to sort the array, all I want to do is sort the perm[] array which is the index list.
To avoid confusions, Heres an example:
perm array holds the initial indices of the original array nums[] (i.e. 0 to nums.length - 1)
I want to move the indices in the perm array based on the data in the nums[] array such that the indices represent the sorted order.
For example :
Array -> (-1,9,-5,3,0)
Initial perm -> (0,1,2,3,4)
After sorting perm based on array - > (2,0,4,3,1)
Here's my code:
import java.util.Arrays;
public class IndexSort {
public boolean leq(Comparable u, Comparable v) {
return u.compareTo(v) <= 0;
}
public void merge(Comparable a[], int temp[], int perm[], int lo, int mid, int hi) {
int i = lo, j = mid + 1;
for (int k = lo; k <= hi; k++) {
temp[k] = perm[k];
}
for (int k = lo; k <= hi; k++) {
if (i > mid) {
perm[k] = temp[j++];
} else if (j > hi) {
perm[k] = temp[i++];
} else if (leq(a[perm[i]], a[perm[j]])) {
perm[k] = temp[i++];
} else {
perm[k] = temp[j++];
}
}
}
public void mergeSort(Comparable a[], int temp[], int perm[], int lo, int hi) {
if (hi <= lo)
return;
int mid = (hi + lo) / 2;
mergeSort(a, temp, perm, lo, mid);
mergeSort(a, temp, perm, mid + 1, hi);
merge(a, temp, perm, lo, mid, hi);
System.out.println(" lo = " + lo + " mid = " + mid + " hi = " + hi);
System.out.println(Arrays.toString(perm));
}
public void indexSort(Comparable nums[], int perm[]) {
int temp[] = new int[nums.length];
Comparable temp2[] = new Comparable[nums.length];
mergeSort(nums, temp, perm, 0, nums.length - 1);
}
public static void main(String[] args) {
IndexSort o1 = new IndexSort();
Comparable nums[] = { 12, -12, 0, 123, -123, 1, 2, 3, 4, -4, -4, -3, -2, 1 };
int perm[] = new int[nums.length];
for (int i = 0; i < perm.length; i++) {
perm[i] = i;
}
System.out.println(Arrays.toString(nums));
System.out.println(Arrays.toString(perm));
o1.indexSort(nums, perm);
System.out.println(Arrays.toString(perm));
}
}
I think this line needs to change from perm to temp:
} else if (leq(a[temp[i]], a[temp[j]])) {
I've been banging my head on the table on this one.
I need to create an n sized array that is optimized for QuickSort Partition. It will be used to demonstrate the growth of QuickSort's best case. I know that for best case, QuickSort must select a pivot that divides the array in half for every recursive call.
I cannot think of a way to create an n-sized optimized array to test. Any help would be greatly appreciated.
Here is the algorithm in Java.
public class QuickSort {
private int length;
private void quickSort(int[] a, int p, int r) {
if (p < r) {
int q = partition(a, p, r);
quickSort(a, p, q - 1);
quickSort(a, q + 1, r);
}
}
private int partition(int[] a, int p, int r) {
int x = a[r];
int i = p - 1;
for (int j = p; j < r; j++) {
if (a[j] <= x) {
i++;
exchange(a, i, j);
}
}
exchange(a, i + 1, r);
return i + 1;
}
public void exchange(int[] a, int i, int j) {
int tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
QuickSort(int[] a) {
if (a == null || a.length == 0) {
return;
}
length = a.length;
quickSort(a, 0, length - 1);
}
}
I know this is an old question, but I had the same question and finally developed a solution. I'm not a Java programmer, so don't blame me for Java code issues, please. I assumed that the quicksort algorithm always takes the first item as a pivot when partitioning.
public class QuickSortBestCase
{
public static void generate(int[] arr, int begin, int end)
{
int count = end - begin;
if(count < 3)
return;
//Find a middle element index
//This will be the pivot element for the part of the array [begin; end)
int middle = begin + (count - 1) / 2;
//Make the left part best-case first: [begin; middle)
generate(arr, begin, middle);
//Swap the pivot and the start element
swap(arr, begin, middle);
//Make the right part best-case, too: (middle; end)
generate(arr, ++middle, end);
}
private static void swap(int[] arr, int i, int j)
{
int t = arr[i];
arr[i] = arr[j];
arr[j] = t;
}
private static void fillArray(int[] arr)
{
for(int i = 0; i != arr.length; ++i)
arr[i] = i + 1;
}
private static void printArray(int[] arr)
{
for(int item : arr)
System.out.print(item + " ");
}
public static void main(String[] args)
{
if(args.length == 0)
return;
int intCount = Integer.parseInt(args[0]);
int[] arr = new int[intCount];
//We basically do what quicksort does in reverse
//1. Fill the array with sorted values from 1 to arr.length
fillArray(arr);
//2. Recursively generate the best-case array for quicksort
generate(arr, 0, arr.length);
printArray(arr);
}
}
This program produces the same output for the array of 15 items, as described here: An example of Best Case Scenario for Quick Sort. And in case someone needs a solution in C++:
template<typename RandomIterator,
typename Compare = std::less<typename RandomIterator::value_type>>
void generate_quicksort_best_case_sorted(RandomIterator begin, RandomIterator end)
{
auto count = std::distance(begin, end);
if (count < 3)
return;
auto middle_index = (count - 1) / 2;
auto middle = begin + middle_index;
//Make the left part best-case first
generate_quicksort_best_case_sorted(begin, middle);
//Swap the pivot and the start element
std::iter_swap(begin, middle);
//Make the right part best-case, too
generate_quicksort_best_case_sorted(++middle, end);
}
template<typename RandomIterator,
typename Compare = std::less<typename RandomIterator::value_type>>
void generate_quicksort_best_case(RandomIterator begin, RandomIterator end)
{
{
auto current = begin;
RandomIterator::value_type value = 1;
while (current != end)
*current++ = value++;
}
generate_quicksort_best_case_sorted(begin, end);
}