【问题标题】:Comparison between 2 different ways of writing Merge Sort两种不同的合并排序方式的比较
【发布时间】:2021-09-26 16:55:07
【问题描述】:

我最近在 C++ 中学习了合并排序算法,并且在教程中遇到了两种不同的实现方式。

第一种方式:

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;
}

第二种方式:

使用temp 数组传入参数。

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方法:-

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

哪种方式的合并排序更好更快?

【问题讨论】:

标签: c++ compare mergesort


【解决方案1】:

一次性分配临时数组并将其作为参数传递将比在每次合并调用时分配临时数组实例快得多。


旁注 - 几乎所有稳定排序的库实现都是某种类型的混合插入排序 + 自底向上合并排序,例如 TimSort。自上而下的归并排序主要用于课堂,而不是图书馆中的实际实现。

【讨论】:

    【解决方案2】:

    第二个实现只分配一次辅助内存并递归传递指针,而第一个实现每次合并调用一次调用分配器和释放器。对于大型数组,第二个代码可能比第一个更快。

    但是请注意,这两个代码都有问题:

    • 他们使用int 作为索引变量,将数组的长度限制为INT_MAX,小于当前硬件的能力。
    • mid 计算为int mid = (start + end) / 2;,这会导致大于INT_MAX / 2 的数组出现算术溢出,从而导致未定义的行为并可能崩溃。 mid 应该这样计算:int mid = start + (end - start) / 2;
    • 他们分配了太多内存:第二个临时数组在第一个代码中没有用,临时数组的大小可能是第二个代码的一半。
    • 他们复制了太多数据:复制后半部分的剩余部分是没有用的,因为这些元素已经到位。
    • 他们没有实现稳定的排序:元素应该与arr[i] &lt;= arr[j]而不是arr[i] &lt; arr[j]进行比较。
    • 自上而下的递归合并排序算法不是最理想的:自下而上的合并排序实现通常更有效,切换到小块的插入排序往往会提高性能,而对排序的子范围进行测试可进一步提高现实生活中的性能。李>

    【讨论】:

    • 对于较小的数组,这两个中的哪一个会更快?
    • 第二种方法即使对于小型数组也应该更快,但只有仔细的基准测试才能给出实际数字,尽管特定于编译器和平台。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-30
    相关资源
    最近更新 更多