How to sort in-place using the merge sort algorithm?

前端 未结 10 1620
心在旅途
心在旅途 2020-11-22 02:21

I know the question is not too specific.

All I want is someone to tell me how to convert a normal merge sort into an in-place merge sort (or a merge sort with const

相关标签:
10条回答
  • 2020-11-22 03:12

    This answer has a code example, which implements the algorithm described in the paper Practical In-Place Merging by Bing-Chao Huang and Michael A. Langston. I have to admit that I do not understand the details, but the given complexity of the merge step is O(n).

    From a practical perspective, there is evidence that pure in-place implementations are not performing better in real world scenarios. For example, the C++ standard defines std::inplace_merge, which is as the name implies an in-place merge operation.

    Assuming that C++ libraries are typically very well optimized, it is interesting to see how it is implemented:

    1) libstdc++ (part of the GCC code base): std::inplace_merge

    The implementation delegates to __inplace_merge, which dodges the problem by trying to allocate a temporary buffer:

    typedef _Temporary_buffer<_BidirectionalIterator, _ValueType> _TmpBuf;
    _TmpBuf __buf(__first, __len1 + __len2);
    
    if (__buf.begin() == 0)
      std::__merge_without_buffer
        (__first, __middle, __last, __len1, __len2, __comp);
    else
      std::__merge_adaptive
       (__first, __middle, __last, __len1, __len2, __buf.begin(),
         _DistanceType(__buf.size()), __comp);
    

    Otherwise, it falls back to an implementation (__merge_without_buffer), which requires no extra memory, but no longer runs in O(n) time.

    2) libc++ (part of the Clang code base): std::inplace_merge

    Looks similar. It delegates to a function, which also tries to allocate a buffer. Depending on whether it got enough elements, it will choose the implementation. The constant-memory fallback function is called __buffered_inplace_merge.

    Maybe even the fallback is still O(n) time, but the point is that they do not use the implementation if temporary memory is available.


    Note that the C++ standard explicitly gives implementations the freedom to choose this approach by lowering the required complexity from O(n) to O(N log N):

    Complexity: Exactly N-1 comparisons if enough additional memory is available. If the memory is insufficient, O(N log N) comparisons.

    Of course, this cannot be taken as a proof that constant space in-place merges in O(n) time should never be used. On the other hand, if it would be faster, the optimized C++ libraries would probably switch to that type of implementation.

    0 讨论(0)
  • 2020-11-22 03:13

    I just tried in place merge algorithm for merge sort in JAVA by using the insertion sort algorithm, using following steps.
    1) Two sorted arrays are available.
    2) Compare the first values of each array; and place the smallest value into the first array.
    3) Place the larger value into the second array by using insertion sort (traverse from left to right).
    4) Then again compare the second value of first array and first value of second array, and do the same. But when swapping happens there is some clue on skip comparing the further items, but just swapping required.

    I have made some optimization here; to keep lesser comparisons in insertion sort.
    The only drawback i found with this solutions is it needs larger swapping of array elements in the second array.

    e.g)

    First___Array : 3, 7, 8, 9

    Second Array : 1, 2, 4, 5

    Then 7, 8, 9 makes the second array to swap(move left by one) all its elements by one each time to place himself in the last.

    So the assumption here is swapping items is negligible compare to comparing of two items.

    https://github.com/skanagavelu/algorithams/blob/master/src/sorting/MergeSort.java

    package sorting;
    
    import java.util.Arrays;
    
    public class MergeSort {
        public static void main(String[] args) {
        int[] array = { 5, 6, 10, 3, 9, 2, 12, 1, 8, 7 };
        mergeSort(array, 0, array.length -1);
        System.out.println(Arrays.toString(array));
    
        int[] array1 = {4, 7, 2};
        System.out.println(Arrays.toString(array1));
        mergeSort(array1, 0, array1.length -1);
        System.out.println(Arrays.toString(array1));
        System.out.println("\n\n");
    
        int[] array2 = {4, 7, 9};
        System.out.println(Arrays.toString(array2));
        mergeSort(array2, 0, array2.length -1);
        System.out.println(Arrays.toString(array2));
        System.out.println("\n\n");
    
        int[] array3 = {4, 7, 5};
        System.out.println(Arrays.toString(array3));
        mergeSort(array3, 0, array3.length -1);
        System.out.println(Arrays.toString(array3));
        System.out.println("\n\n");
    
        int[] array4 = {7, 4, 2};
        System.out.println(Arrays.toString(array4));
        mergeSort(array4, 0, array4.length -1);
        System.out.println(Arrays.toString(array4));
        System.out.println("\n\n");
    
        int[] array5 = {7, 4, 9};
        System.out.println(Arrays.toString(array5));
        mergeSort(array5, 0, array5.length -1);
        System.out.println(Arrays.toString(array5));
        System.out.println("\n\n");
    
        int[] array6 = {7, 4, 5};
        System.out.println(Arrays.toString(array6));
        mergeSort(array6, 0, array6.length -1);
        System.out.println(Arrays.toString(array6));
        System.out.println("\n\n");
    
        //Handling array of size two
        int[] array7 = {7, 4};
        System.out.println(Arrays.toString(array7));
        mergeSort(array7, 0, array7.length -1);
        System.out.println(Arrays.toString(array7));
        System.out.println("\n\n");
    
        int input1[] = {1};
        int input2[] = {4,2};
        int input3[] = {6,2,9};
        int input4[] = {6,-1,10,4,11,14,19,12,18};
        System.out.println(Arrays.toString(input1));
        mergeSort(input1, 0, input1.length-1);
        System.out.println(Arrays.toString(input1));
        System.out.println("\n\n");
    
        System.out.println(Arrays.toString(input2));
        mergeSort(input2, 0, input2.length-1);
        System.out.println(Arrays.toString(input2));
        System.out.println("\n\n");
    
        System.out.println(Arrays.toString(input3));
        mergeSort(input3, 0, input3.length-1);
        System.out.println(Arrays.toString(input3));
        System.out.println("\n\n");
    
        System.out.println(Arrays.toString(input4));
        mergeSort(input4, 0, input4.length-1);
        System.out.println(Arrays.toString(input4));
        System.out.println("\n\n");
    }
    
    private static void mergeSort(int[] array, int p, int r) {
        //Both below mid finding is fine.
        int mid = (r - p)/2 + p;
        int mid1 = (r + p)/2;
        if(mid != mid1) {
            System.out.println(" Mid is mismatching:" + mid + "/" + mid1+ "  for p:"+p+"  r:"+r);
        }
    
        if(p < r) {
            mergeSort(array, p, mid);
            mergeSort(array, mid+1, r);
    //      merge(array, p, mid, r);
            inPlaceMerge(array, p, mid, r);
            }
        }
    
    //Regular merge
    private static void merge(int[] array, int p, int mid, int r) {
        int lengthOfLeftArray = mid - p + 1; // This is important to add +1.
        int lengthOfRightArray = r - mid;
    
        int[] left = new int[lengthOfLeftArray];
        int[] right = new int[lengthOfRightArray];
    
        for(int i = p, j = 0; i <= mid; ){
            left[j++] = array[i++];
        }
    
        for(int i = mid + 1, j = 0; i <= r; ){
            right[j++] = array[i++];
        }
    
        int i = 0, j = 0;
        for(; i < left.length && j < right.length; ) {
            if(left[i] < right[j]){
                array[p++] = left[i++];
            } else {
                array[p++] = right[j++];
            }
        }
        while(j < right.length){
            array[p++] = right[j++];
        } 
        while(i < left.length){
            array[p++] = left[i++];
        }
    }
    
    //InPlaceMerge no extra array
    private static void inPlaceMerge(int[] array, int p, int mid, int r) {
        int secondArrayStart = mid+1;
        int prevPlaced = mid+1;
        int q = mid+1;
        while(p < mid+1 && q <= r){
            boolean swapped = false;
            if(array[p] > array[q]) {
                swap(array, p, q);
                swapped = true;
            }   
            if(q != secondArrayStart && array[p] > array[secondArrayStart]) {
                swap(array, p, secondArrayStart);
                swapped = true;
            }
            //Check swapped value is in right place of second sorted array
            if(swapped && secondArrayStart+1 <= r && array[secondArrayStart+1] < array[secondArrayStart]) {
                prevPlaced = placeInOrder(array, secondArrayStart, prevPlaced);
            }
            p++;
            if(q < r) {     //q+1 <= r) {
                q++;
            }
        }
    }
    
    private static int placeInOrder(int[] array, int secondArrayStart, int prevPlaced) {
        int i = secondArrayStart;
        for(; i < array.length; i++) {
            //Simply swap till the prevPlaced position
            if(secondArrayStart < prevPlaced) {
                swap(array, secondArrayStart, secondArrayStart+1);
                secondArrayStart++;
                continue;
            }
            if(array[i] < array[secondArrayStart]) {
                swap(array, i, secondArrayStart);
                secondArrayStart++;
            } else if(i != secondArrayStart && array[i] > array[secondArrayStart]){
                break;
            }
        }
        return secondArrayStart;
    }
    
    private static void swap(int[] array, int m, int n){
        int temp = array[m];
        array[m] = array[n];
        array[n] = temp;
    }
    }
    
    0 讨论(0)
  • 2020-11-22 03:17

    An example of bufferless mergesort in C.

    #define SWAP(type, a, b) \
        do { type t=(a);(a)=(b);(b)=t; } while (0)
    
    static void reverse_(int* a, int* b)
    {
        for ( --b; a < b; a++, b-- )
           SWAP(int, *a, *b);
    }
    static int* rotate_(int* a, int* b, int* c)
    /* swap the sequence [a,b) with [b,c). */
    {
        if (a != b && b != c)
         {
           reverse_(a, b);
           reverse_(b, c);
           reverse_(a, c);
         }
        return a + (c - b);
    }
    
    static int* lower_bound_(int* a, int* b, const int key)
    /* find first element not less than @p key in sorted sequence or end of
     * sequence (@p b) if not found. */
    {
        int i;
        for ( i = b-a; i != 0; i /= 2 )
         {
           int* mid = a + i/2;
           if (*mid < key)
              a = mid + 1, i--;
         }
        return a;
    }
    static int* upper_bound_(int* a, int* b, const int key)
    /* find first element greater than @p key in sorted sequence or end of
     * sequence (@p b) if not found. */
    {
        int i;
        for ( i = b-a; i != 0; i /= 2 )
         {
           int* mid = a + i/2;
           if (*mid <= key)
              a = mid + 1, i--;
         }
        return a;
    }
    
    static void ip_merge_(int* a, int* b, int* c)
    /* inplace merge. */
    {
        int n1 = b - a;
        int n2 = c - b;
    
        if (n1 == 0 || n2 == 0)
           return;
        if (n1 == 1 && n2 == 1)
         {
           if (*b < *a)
              SWAP(int, *a, *b);
         }
        else
         {
           int* p, * q;
    
           if (n1 <= n2)
              p = upper_bound_(a, b, *(q = b+n2/2));
           else
              q = lower_bound_(b, c, *(p = a+n1/2));
           b = rotate_(p, b, q);
    
           ip_merge_(a, p, b);
           ip_merge_(b, q, c);
         }
    }
    
    void mergesort(int* v, int n)
    {
        if (n > 1)
         {
           int h = n/2;
           mergesort(v, h); mergesort(v+h, n-h);
           ip_merge_(v, v+h, v+n);
         }
    }
    

    An example of adaptive mergesort (optimized).

    Adds support code and modifications to accelerate the merge when an auxiliary buffer of any size is available (still works without additional memory). Uses forward and backward merging, ring rotation, small sequence merging and sorting, and iterative mergesort.

    #include <stdlib.h>
    #include <string.h>
    
    static int* copy_(const int* a, const int* b, int* out)
    {
        int count = b - a;
        if (a != out)
           memcpy(out, a, count*sizeof(int));
        return out + count;
    }
    static int* copy_backward_(const int* a, const int* b, int* out)
    {
        int count = b - a;
        if (b != out)
           memmove(out - count, a, count*sizeof(int));
        return out - count;
    }
    
    static int* merge_(const int* a1, const int* b1, const int* a2,
      const int* b2, int* out)
    {
        while ( a1 != b1 && a2 != b2 )
           *out++ = (*a1 <= *a2) ? *a1++ : *a2++;
        return copy_(a2, b2, copy_(a1, b1, out));
    }
    static int* merge_backward_(const int* a1, const int* b1,
      const int* a2, const int* b2, int* out)
    {
        while ( a1 != b1 && a2 != b2 )
           *--out = (*(b1-1) > *(b2-1)) ? *--b1 : *--b2;
        return copy_backward_(a1, b1, copy_backward_(a2, b2, out));
    }
    
    static unsigned int gcd_(unsigned int m, unsigned int n)
    {
        while ( n != 0 )
         {
           unsigned int t = m % n;
           m = n;
           n = t;
         }
        return m;
    }
    static void rotate_inner_(const int length, const int stride,
      int* first, int* last)
    {
        int* p, * next = first, x = *first;
        while ( 1 )
         {
           p = next;
           if ((next += stride) >= last)
              next -= length;
           if (next == first)
              break;
           *p = *next;
         }
        *p = x;
    }
    static int* rotate_(int* a, int* b, int* c)
    /* swap the sequence [a,b) with [b,c). */
    {
        if (a != b && b != c)
         {
           int n1 = c - a;
           int n2 = b - a;
    
           int* i = a;
           int* j = a + gcd_(n1, n2);
    
           for ( ; i != j; i++ )
              rotate_inner_(n1, n2, i, c);
         }
        return a + (c - b);
    }
    
    static void ip_merge_small_(int* a, int* b, int* c)
    /* inplace merge.
     * @note faster for small sequences. */
    {
        while ( a != b && b != c )
           if (*a <= *b)
              a++;
           else
            {
              int* p = b+1;
              while ( p != c && *p < *a )
                 p++;
              rotate_(a, b, p);
              b = p;
            }
    }
    static void ip_merge_(int* a, int* b, int* c, int* t, const int ts)
    /* inplace merge.
     * @note works with or without additional memory. */
    {
        int n1 = b - a;
        int n2 = c - b;
    
        if (n1 <= n2 && n1 <= ts)
         {
           merge_(t, copy_(a, b, t), b, c, a);
         }
        else if (n2 <= ts)
         {
           merge_backward_(a, b, t, copy_(b, c, t), c);
         }
        /* merge without buffer. */
        else if (n1 + n2 < 48)
         {
           ip_merge_small_(a, b, c);
         }
        else
         {
           int* p, * q;
    
           if (n1 <= n2)
              p = upper_bound_(a, b, *(q = b+n2/2));
           else
              q = lower_bound_(b, c, *(p = a+n1/2));
           b = rotate_(p, b, q);
    
           ip_merge_(a, p, b, t, ts);
           ip_merge_(b, q, c, t, ts);
         }
    }
    static void ip_merge_chunk_(const int cs, int* a, int* b, int* t,
      const int ts)
    {
        int* p = a + cs*2;
        for ( ; p <= b; a = p, p += cs*2 )
           ip_merge_(a, a+cs, p, t, ts);
        if (a+cs < b)
           ip_merge_(a, a+cs, b, t, ts);
    }
    
    static void smallsort_(int* a, int* b)
    /* insertion sort.
     * @note any stable sort with low setup cost will do. */
    {
        int* p, * q;
        for ( p = a+1; p < b; p++ )
         {
           int x = *p;
           for ( q = p; a < q && x < *(q-1); q-- )
              *q = *(q-1);
           *q = x;
         }
    }
    static void smallsort_chunk_(const int cs, int* a, int* b)
    {
        int* p = a + cs;
        for ( ; p <= b; a = p, p += cs )
           smallsort_(a, p);
        smallsort_(a, b);
    }
    
    static void mergesort_lower_(int* v, int n, int* t, const int ts)
    {
        int cs = 16;
        smallsort_chunk_(cs, v, v+n);
        for ( ; cs < n; cs *= 2 )
           ip_merge_chunk_(cs, v, v+n, t, ts);
    }
    
    static void* get_buffer_(int size, int* final)
    {
        void* p = NULL;
        while ( size != 0 && (p = malloc(size)) == NULL )
           size /= 2;
        *final = size;
        return p;
    }
    void mergesort(int* v, int n)
    {
        /* @note buffer size may be in the range [0,(n+1)/2]. */
        int request = (n+1)/2 * sizeof(int);
        int actual;
        int* t = (int*) get_buffer_(request, &actual);
    
        /* @note allocation failure okay. */
        int tsize = actual / sizeof(int);
        mergesort_lower_(v, n, t, tsize);
        free(t);
    }
    
    0 讨论(0)
  • 2020-11-22 03:18

    It really isn't easy or efficient, and I suggest you don't do it unless you really have to (and you probably don't have to unless this is homework since the applications of inplace merging are mostly theoretical). Can't you use quicksort instead? Quicksort will be faster anyway with a few simpler optimizations and its extra memory is O(log N).

    Anyway, if you must do it then you must. Here's what I found: one and two. I'm not familiar with the inplace merge sort, but it seems like the basic idea is to use rotations to facilitate merging two arrays without using extra memory.

    Note that this is slower even than the classic merge sort that's not inplace.

    0 讨论(0)
提交回复
热议问题