1

I have learned Merge Sort algorithm in C++ recently and have come across 2 different ways by which it is implemented in tutorials.

1st way:

void merge(int arr[], int low, int mid, int high) {
    const int n1 = (mid - low + 1);
    const int n2 = (high - mid);
    int *a = new int[n1], *b = new int[n2];//dynamically allocated because of MSVC compiler
    for (int i = 0; i < n1; i++)
        a[i] = arr[low + i];
    for (int i = 0; i < n2; i++)
        b[i] = arr[mid + 1 + i];
    int i = 0, j = 0, k = low;
    while (i < n1 && j < n2) {
        if (a[i] < b[j]) {
            arr[k] = a[i];
            i++;
        } else {
            arr[k] = b[j];
            j++;
        }
        k++;
    }
    while (i < n1) {
        arr[k] = a[i];
        k++, i++;
    }
  
    while (j < n2) {
        arr[k] = b[j];
        k++, j++;
    }
    delete[] a;
    delete[] b;
}

void mergeSort(int arr[], int start, int end) {
     if (start < end) {
        int mid = (start + end) / 2;
        mergeSort(arr, start, mid);
        mergeSort(arr, mid + 1, end);
        merge(arr, start, mid, end);
    }
}

int main() {
    int arr[] = { 9, 14, 4, 8, 6, 7, 5, 2, 1 };
    unsigned size = sizeof(arr) / sizeof(arr[0]);
    printArray(arr, size);
    mergeSort(arr, 0, size - 1);
    printArray(arr, size);
    return 0;
}

2nd way:

Using temp array passed in the arguments.

void merge(int arr[], int temp[], int low, int mid, int high) {
    int i = low, k = low, j = mid + 1;
    while (i <= mid && j <= high) {
        if (arr[i] < arr[j]) {
            temp[k] = arr[i];
            i++;
        } else {
            temp[k] = arr[j];
            j++;
        }
        k++;
    }
    while (i <= mid) {
        temp[k] = arr[i];
        k++, i++;
    }
    while (j <= high) {
        temp[k] = arr[j];
        k++, j++;
    }
    for (int i = low; i <= high; i++)
        arr[i] = temp[i];
}

void mergeSort(int arr[], int temp[], int start, int end) {
    if (start < end) {
        int mid = (start + end) / 2;
        mergeSort(arr, temp, start, mid);
        mergeSort(arr, temp, mid + 1, end);
        merge(arr, temp, start, mid, end);
    }
}

int main() {
    int arr[] = { 9, 14, 4, 8, 6, 7, 5, 2, 1 };
    unsigned size = sizeof(arr) / sizeof(arr[0]);
    int *buffer = new int[size];
    printArray(arr, size);
    mergeSort(arr, buffer, 0, size - 1);
    printArray(arr, size);
    delete[] buffer;
    return 0;
}

printArray method:-

void printArray(int arr[], int n) {
    for (int i = 0; i < n; i++)
        printf("%d ", arr[i]);
    printf("\n");
}

Which way of writing Merge Sort is better and faster ?

chqrlie
  • 131,814
  • 10
  • 121
  • 189
DIVIJ_404
  • 13
  • 3
  • 1
    Well, you could experiment and [measure](https://stackoverflow.com/questions/22387586/measuring-execution-time-of-a-function-in-c) yourself? – πάντα ῥεῖ Sep 26 '21 at 16:56
  • Does this answer your question? [Measuring execution time of a function in C++](https://stackoverflow.com/questions/22387586/measuring-execution-time-of-a-function-in-c) – Wais Kamal Sep 26 '21 at 17:01

2 Answers2

1

Doing a one time allocation of the temp array and passing it as a parameter will be significantly faster than allocating an instance of a temp array on every merge call.


Side note - Almost all library implementations for stable sorts are some type of hybrid insertion sort + bottom up merge sort such as TimSort. Top down merge sort is mostly used for classrooms, not actual implementations in libraries.

rcgldr
  • 27,407
  • 3
  • 36
  • 61
0

The second implementation allocates ancillary memory just once and passes the pointer recursively whereas the first calls the allocator and deallocator once per merge call. For large arrays, the second code is likely faster than the first.

Note however that both codes have problems:

  • they use int for the index variables, limiting the length of arrays to INT_MAX, which is smaller than capabilities of current hardware.
  • the compute mid as int mid = (start + end) / 2; which causes an arithmetic overflow for arrays larger than INT_MAX / 2, leading to undefined behavior and likely crashes. mid should be computed this way: int mid = start + (end - start) / 2;
  • they allocate too much memory: the second temporary array is useless in the first code and the temporary array could be half the size for the second code.
  • they copy too much data: copying the remainder of the second half is useless as these elements are already in place.
  • they do not implement stable sorting: elements should be compared with arr[i] <= arr[j] instead of arr[i] < arr[j].
  • the top down recursive merge sort algorithm is suboptimal: bottom-up merge sort implementations are usually more efficient, switching to insertion sort for small chunks tends to improve performance and testing for sorted subranges further improves performance in real life cases.
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • Which one of the 2 will be faster on average case with smaller arrays? – DIVIJ_404 Oct 07 '21 at 16:39
  • The second approach should be faster even for small arrays, but only careful benchmarking will give actual figures, albeit specific to a compiler and platform. – chqrlie Oct 07 '21 at 17:20