5
\$\begingroup\$

I decided to write my own implementation of merge sort. Here is the code:

public static int[] mergeSort(int[] array) { if (array.length <= 1) { return array; } int half = array.length / 2; return merge(mergeSort(Arrays.copyOfRange(array, 0, half)), mergeSort(Arrays.copyOfRange(array, half, array.length))); } private static int[] merge(int[] array1, int[] array2) { int len1 = array1.length, len2 = array2.length; int totalLength = len1 + len2; int[] result = new int[totalLength]; for(int i = 0, i1 = 0, i2 = 0; i < totalLength; ) { if(i1 >= len1) { while(i2 < len2) { result[i++] = array2[i2++]; } break; } else if(i2 >= len2) { while(i1 < len1) { result[i++] = array1[i1++]; } break; } if(array1[i1] > array2[i2]) { result[i++] = array2[i2++]; } else { result[i++] = array1[i1++]; } } return result; } 

When used like:

public static void main(String[] args) { Random random = new Random(); int[] array = new int[10000]; for(int i = 0, len = array.length; i < len; i++) { array[i] = random.nextInt(25000); } long before = System.nanoTime(); System.out.println(toString(array) + "\n\n" + toString(mergeSort(array)) + "\nSorted in: " + (System.nanoTime() - before) + " nanoseconds."); } private static String toString(int[] array) { StringBuffer sb = new StringBuffer("[ "); int len = array.length; for(int i = 0; i < len - 1; i++) { sb.append(array[i] + ", "); } sb.append(array[len - 1] + " ]"); return sb.toString(); } 

A sample output would be:

[ 8828, 11730, 24944, 18351, 24154, 12634, ... 18114, 3517, 221, 1808, 10474, 18710, 10050, 505, 11304, 5945, 19927, 652 ] [ 0, 14, 21, 23, 24, 25, 27, 35, 35, ... 24926, 24928, 24929, 24931, 24932, 24935, 24936, 24937, 24937, 24940, 24940, 24992 ] Sorted in 79326188 nanoseconds. 

Another sample output:

[ 11905, 14630, 17973, 9145, 10181, 20421, 24063, 13459, 5806, 22352, 1880, 24100, ... 14299, 15733, 2189, 16743, 6046, 331, 11915, 4008, 14482, 3785, 921, 13954 ] [ 3, 5, 8, 9, 9, 14, 17, 17, 18, 19, 21, 23, 26, 27, 28, 28, 30, 35, 36, 39, 44, 46, 47, 48, 50, 54, ... 24982, 24983, 24983, 24987, 24989, 24991, 24993, 24995, 24997 ] Sorted in 23808700 nanoseconds. 

The average time needed to sort each number is ((79326188 / 10000) + (23808700 / 10000)) / 2 = 515.67444 nanoseconds, which to me seems a bit slow. Is this implementation as optimized as it can get, or is there a faster way?

\$\endgroup\$

    3 Answers 3

    2
    \$\begingroup\$

    You keep allocating a new array on each split and merge, this totals up to a lot of allocations on short lived arrays.

    What you can do is create a private function with more parameters so you can preallocate the merge buffer:

    public void mergeSort(int[] array){ mergeSort(array, new int[array.length], 0, array.length); } private void mergeSort(int[] array, int[] buffer, int start, int end){ if(start+1< end){ mergeSort(array, buffer, start, (start+end)/2); mergeSort(array, buffer, (start+end)/2, end); merge(array, buffer, start, (start+end)/2, end); } } private void merge(int[] array, int[] buffer, int start, int mid, int end){ System.arrayCopy(array, start, buffer, start, end-start); //merge implementation int index1=start, index2=mid; int resultindex = start; while(index1<mid && index2<end){ if(buffer[index1]<buffer[index2]){ array[resultindex++] = buffer[index1++] }else{ array[resultindex++] = buffer[index2++] } } if(index1<mid) System.arrayCopy(buffer, index1, array, resultIndex, mid-index1); if(index2<end) System.arrayCopy(buffer, index2, array, resultIndex, end-index2); } 
    \$\endgroup\$
      4
      \$\begingroup\$

      General suggestion not related to the sorting implementation: You should be timing the time it takes to call and return from your mergeSort() method, not the time it takes to call System.out.println() and your toString() methods as well. Furthermore, a better microbenchmark calls for running a few iterations and then taking averages to smooth out outlying cases due to "warm-up".

      Also, I will suggest using a StringBuilder instead of StringBuffer since your presumably do not require synchronization here, and append values one by one instead of concatenating them together:

      stringBuilder.append(array[i]).append(", "); 

      Even if you feel like timing how long it takes for your toString() method, doing both of them is likely to shave a bit of time off too if used with enough repetition.

      \$\endgroup\$
        1
        \$\begingroup\$

        Apart from review comments given by h.j.k , Your merge method can be written efficiently as given in URL which is optimized varient for merge method.After modifing the merge as mentioned in URL it reduced the overall sorting time for me.

        One advice when you want to benchmark the sorting time then you should provide the constant input values . otherWise every time you will get different time as you are fetching random numbers . But as I cannot paste all the 10000, static numbers here So I have used the same strategy as used by you for getting input array .

        Actual time taken to sort with the original merging strategy in consecutive runs :

        12936403 13193972 13553510 13535302 13079092 13497560 

        Actual time taken to sort with the modified merging strategy in consecutive runs :

        9831666 10185245 10208088 10277281 10314692 10037920 

        Please find below modified code:

        package com.study.algorithms; import java.util.Arrays; import java.util.Random; public class MergeSort { public static void main(String[] args) { int[] array = getIntArray(); long before = System.nanoTime(); int [] sortedArray= mergeSort(array); System.out.println("Sorting took "+ (System.nanoTime() - before) +" nanoseconds "); System.out.println(toString(array) + "\n\n" + toString(sortedArray) + "\n main method completed in: " + (System.nanoTime() - before) + " nanoseconds."); } private static String toString(int[] array) { StringBuilder sb = new StringBuilder("[ "); int len = array.length; for(int i = 0; i < len - 1; i++) { sb.append(array[i] + ", "); } sb.append(array[len - 1] + " ]"); return sb.toString(); } public static int[] mergeSort(int[] array) { if (array.length <= 1) { return array; } int half = array.length / 2; return merge(mergeSort(Arrays.copyOfRange(array, 0, half)), mergeSort(Arrays.copyOfRange(array, half, array.length))); } private static int[] merge(int[] left, int[] right) { int len1 = left.length, len2 = right.length; int totalLength = len1 + len2; int[] result = new int[totalLength]; /* ORIGINAL MERGING STRATEGY */ /*for(int i = 0, i1 = 0, i2 = 0; i < totalLength; ) { if(i1 >= len1) { while(i2 < len2) { result[i++] = right[i2++]; } break; } else if(i2 >= len2) { while(i1 < len1) { result[i++] = left[i1++]; } break; } if(left[i1] > right[i2]) { result[i++] = right[i2++]; } else { result[i++] = left[i1++]; } } */ /* MODIFIED MERGING STRATEGY */ int counterForLeft =0,counterForRight=0,resultIndex=0; while(counterForLeft<len1 || counterForRight < len2){ if(counterForLeft<len1 && counterForRight < len2){ if(left[counterForLeft]<= right[counterForRight]){ result[resultIndex++] =left[counterForLeft++]; } else { result[resultIndex++] =right[counterForRight++]; } }else if(counterForLeft<len1){ result[resultIndex++] = left[counterForLeft++]; }else if (counterForRight <len2){ result[resultIndex++] =right[counterForRight++]; } } return result; } private static int[] getIntArray() { int[] array = new int[10000]; Random random = new Random(); for(int i = 0, len = array.length; i < len; i++) { array[i] = random.nextInt(25000); } return array; } } 
        \$\endgroup\$
        4
        • \$\begingroup\$I'm sorry, but it does not work.\$\endgroup\$CommentedSep 5, 2014 at 0:59
        • \$\begingroup\$Please share the way input you tried.. Do you mean to say program didn't sorted numbers correctly or program is still slow ...\$\endgroup\$CommentedSep 5, 2014 at 2:35
        • \$\begingroup\$Sorry, never mind that comment. It works now, but it is slower than the solution @ratchetfreak provided me.\$\endgroup\$CommentedSep 5, 2014 at 3:30
        • \$\begingroup\$I dont know if you have downvoted my answer or not,if yes would you mind upvoting that because it is still faster than your algorithm.\$\endgroup\$CommentedSep 5, 2014 at 3:50

        Start asking to get answers

        Find the answer to your question by asking.

        Ask question

        Explore related questions

        See similar questions with these tags.