简体   繁体   中英

Java implementation of MergeSort; cannot find the bug

Ok, this is one of those desperate questions. I'm trying to implement Bottom-Up MS to sort and integer array. But goodness sake, I can't seem to find the bug...

import java.util.Scanner;

public class A2 {

    public static boolean less(Integer v, Integer w) {
        return v.compareTo(w) < 0;
    }

    public static void sort(int[] a) {
        int N = a.length;
        int[] aux = new int[N];
        for (int sz = 1; sz < N; sz = sz + sz)
            for (int lo = 0; lo < N - sz; lo += sz + sz)
                merge(a, aux, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, N - 1));
    }

    public static void merge(int[] a, int aux[], int lo, int mid, int hi) {
        int i = lo;
        int j = mid + 1;

        for (int k = lo; k <= hi; k++)
            aux[k] = a[k];

        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[j], aux[i]))
                a[k] = a[j++];
            else
                a[k] = a[i++];

    }

    public static void main(String[] args) {
        int next = 0;
        Scanner scanner = new Scanner(System.in);
        int size = Integer.parseInt(scanner.nextLine());
        int[] v = new int[size];
        String s = scanner.nextLine();
        scanner.close();
        String[] sa = s.split("[\\s]+");
        while (next < size) {
            v[next] = Integer.parseInt(sa[next]);
            next ++;
        }
        for (Integer i : v)
            System.out.print(i + " ");
        System.out.println();
        System.out.println("----------------------------------");
        sort(v);
        for (int i = 0; i < size; i++)
            System.out.print(v[i] + " ");
        System.out.println();
    }
}

In the main function, I print the elements of the array, just to be sure that the problem is in the sorting. The first number is just the size of the array. The bug is either in the sort() or in the merge() . Here's some sample outputs:

9
10 45 20 5 -6 80 99 -4 0
10 45 20 5 -6 80 99 -4 0 
----------------------------------
-6 -4 -4 -6 -4 -4 -6 0 99 

6
6 7 3 2 4 1
6 7 3 2 4 1 
----------------------------------
1 1 1 4 6 7 

5
6 5 2 3 4
6 5 2 3 4 
----------------------------------
2 3 4 5 6 

This last one seems just fine.

Please help me, I've been going around and around and I cannot seem to find the bug.

The problem is in the merge() method: in the last 2 cases in the loop, you copy values from a instead of from aux . It is not a problem when you copy a[j++] but when you copy a[i++] the value may have been overwritten already.

Considering that the values in the right slice are only written after they have already been copied, you only need to save the left slice.

Here is a modified version with this simplification:

    public static void merge(int[] a, int aux[], int lo, int mid, int hi) {
        int i = lo;
        int j = mid + 1;

        for (int k = lo; k <= mid; k++)  // save a[lo..mid] to aux
            aux[k] = a[k];

        for (int k = lo; k <= hi; k++) {
            if (i > mid)
                a[k] = a[j++];
            else if (j > hi)
                a[k] = aux[i++];
            else if (less(a[j], aux[i]))
                a[k] = a[j++];
            else
                a[k] = aux[i++];
        }
    }

Note that it would be less error prone to consider mid to be the start of the right slice and hi to be the index one past the end of the slice. The sort() loop would be simpler, without tricky +/-1 adjustments. Incidentally the inner loop test in your version is off by one, albeit without consequences aside from inefficiency. It should be:

for (int lo = 0; lo < N - sz - 1; lo += sz + sz)

Here is a further simplified implementation with included/excluded slices and an combined test:

    public static void sort(int[] a) {
        int N = a.length;
        int[] aux = new int[N];
        for (int sz = 1; sz < N; sz = sz + sz)
            for (int lo = 0; lo < N - sz; lo += sz + sz)
                merge(a, aux, lo, lo + sz, Math.min(lo + sz + sz, N));
    }

    public static void merge(int[] a, int aux[], int lo, int mid, int hi) {
        for (int i = lo; i < mid; i++) { // save a[lo..mid[ to aux
            aux[i] = a[i];
        }
        for (int i = lo, j = mid, k = lo; i < mid; k++) {
            if (j < hi && less(a[j], aux[i]))
                a[k] = a[j++];
            else
                a[k] = aux[i++];
        }
    }

This version is very simple but still not very efficient on large arrays because each pass goes through the whole array, defeating the processor cacheing scheme. It would be more efficient to perform bottom up merging incrementally, using a stack of sorted subarrays of increasing sizes.

With this change, it works on my system.

            else if(less(aux[j], aux[i]))
                a[k] = aux[j++];             // fix  (aux)
            else
                a[k] = aux[i++];             // fix  (aux)

If the merge sort was avoiding a copy step by changing the direction of merge with each pass, if there is a single run left over at the end of a merge pass, it needs to be copied. The 3rd section in this answer has an example of this.


The usage of less(...) intermittently doubles the run time on my system when I tested using larger arrays (like 8 million ints) with random values. Changing if(less(aux[j], aux[i])) to if(aux[j] < aux[i]) seems to have fixed this or made it very rare.


Example code for a somewhat more efficient merge sort, which avoids doing copies unless there is an odd number of passes. This could be avoided by calculating number of passes first, and if the number of passes is odd, swap in place. This could be extended to larger sub-groups by using insertion sort on groups of 32 or 64 elements on the initial pass.

    public static void sort(int[] a) {
        int n = a.length;
        if(n < 2)
            return;
        int[] dst = new int[n];
        int[] src = a;
        int[] tmp;
        for(int sz = 1; sz < n; sz = sz+sz){
            int lo;
            int md;
            int hi = 0;
            while(hi < n){
                lo = hi;
                md = lo+sz;
                if(md >= n){            // if single run remaining, copy it
                    System.arraycopy(src, lo, dst, lo, n-lo);
                    break;
                }
                hi = md+sz;
                if(hi > n)
                    hi = n;
                merge(src, dst, lo, md, hi);
            }
            tmp = src;                  // swap references
            src = dst;                  //  to change direction of merge
            dst = tmp;
        }
        if(src != a)                    // copy back to a if needed
            System.arraycopy(src, 0, a, 0, n);
    }

    public static void merge(int[] src, int[] dst, int lo, int md, int hi) {
        int i = lo;
        int j = md;
        int k = lo;
        while(true){
            if(src[j]< src[i]){
                dst[k++] = src[j++];
                if(j < hi)
                    continue;
                System.arraycopy(src, i, dst, k, md-i);
                return;
            } else {
                dst[k++] = src[i++];
                if(i < md)
                    continue;
                System.arraycopy(src, j, dst, k, hi-j);
                return;
            }
        }
    }

you can try with this code:

import java.util.Arrays;

public class MergeSort
{
   public static void merge(double[] a, 
                            int iLeft, int iMiddle, int iRight, 
                            double[] tmp)
   {
      int i, j, k;

      i = iLeft;
      j = iMiddle;
      k = iLeft;

      while ( i < iMiddle || j < iRight )
      {
         if ( i < iMiddle && j < iRight )
         {  // Both array have elements
            if ( a[i] < a[j] )
               tmp[k++] = a[i++];
            else
               tmp[k++] = a[j++];
         }
         else if ( i == iMiddle )
            tmp[k++] = a[j++];     // a is empty
         else if ( j == iRight )
            tmp[k++] = a[i++];     // b is empty
      }

      /* =================================
         Copy tmp[] back to a[]
         ================================= */
      for ( i = iLeft; i < iRight; i++ )
         a[i] = tmp[i];
   }

   public static void sort(double[] a, double[] tmp)
   {
      int width;

      for ( width = 1; width < a.length; width = 2*width )
      {
         // Combine sections of array a of width "width"
         int i;

         for ( i = 0; i < a.length; i = i + 2*width )
         {
            int left, middle, right;

        left = i;
        middle = i + width;
        right  = i + 2*width;

            merge( a, left, middle, right, tmp );

         }

         System.out.println("After 1 iter: " + Arrays.toString(a) );
      }
   }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM