【问题标题】:Recursive mergesort in C not modifying the original arrayC中的递归合并排序不修改原始数组
【发布时间】:2020-02-05 17:05:02
【问题描述】:

我在 C 中实现合并排序。我有一个合并函数 - merge(int array[], int start, int middle, int end) 和一个合并排序函数 - mergeSort(int *array, unsigned int size)

如果对原始数组的前半部分和原始数组的后半部分进行了排序(例如:5,6,7,8,1,2,3,4),则合并排序非常有效。这是因为无论如何我的合并函数都通过了原始数组,并且在给定 2 个排序数组时它可以工作(如预期的那样)。我的问题是他们不在的时候。每次我调用合并时,我的原始数组都不会被修改,即使我已经对其进行了编程。谁能弄清楚我的问题在哪里?代码如下。

当我在输入 {10,9,8,7,6,5,4,3,2,1} 上运行此代码时,它会返回 {5,4,3,2,1,0,10,9,8,7,6}

void merge(int arr[], int l, int m, int r) {

    int size1 = m - l + 1;
    int size2 = r - m;

    int arr1[size1];
    int arr2[size2];

    int i;

    for ( i = 0; i < size1; i++ ) {
        arr1[i] = arr[l + i];
    }

    for ( i = 0; i < size2; i++ ) {
        arr2[i] = arr[m + i + 1];
    }

    i = 0;
    int j = 0;
    int k = 0;

    while ( i < size1 && j < size2 ) {
        if ( arr1[i] < arr2[j] ) {
            arr[k] = arr1[i];
            i++;
            k++;
        } else {
            arr[k] = arr2[j];
            j++;
            k++;
        }
    }
    while ( i < size1 ) {
        arr[k] = arr1[i];
        i++;
        k++;
    }
    while ( j < size2 ) {
        arr[k] = arr2[j];
        j++;
        k++;
    }
}

void mergeSort(int *array, unsigned int size) {
    int start = 0;
    int middle = (size / 2) - 1;
    int end = size - 1;

    if ( size < 2 ) {
        return;
    }

    int m = ( size / 2 );

    int arr1[m];
    int arr2[size - m];

    int i;

    for ( i = 0; i < middle + 1; i++ ) {
        arr1[i] = array[i];
        printf("%d\n", arr1[i]);
    }

    for ( i = middle + 1; i < size; i++ ) {
        arr2[i - (middle + 1)] = array[i];
    }

    mergeSort(arr1, m);
    mergeSort(arr2, size - m);
    merge(array, start, middle, end);
}

【问题讨论】:

    标签: c arrays mergesort


    【解决方案1】:

    mergeSort 中,在执行mergeSort(arr1, m)mergeSort(arr2, size - m) 之后,您实际上并没有对arr1arr2 执行任何操作。

    对于一个简单的修复,我建议不要使用变量 arr1arr2 并直接在 array 的部分上调用 mergeSort,如下所示:

    void mergeSort(int* array, unsigned int size) {
        int start = 0;
        int middle = (size / 2);
        int end = size - 1;
    
        if ( size < 2 ) {
            return;
        }
    
        mergeSort(array, middle);
        mergeSort(array + middle, size - middle);
        merge(array, start, middle - 1, end);
    }
    

    【讨论】:

    • 这就是全部代码吗?我知道递归可以产生非常优雅的算法,但这似乎不切实际地简洁。似乎您实际上并没有对 array 做任何事情。
    • @RobertHarvey 好吧,有整个merge 功能部分(OP 给出了正确的实现)。连同它一起,它是工作代码(尽管它可能会遇到非常大的数组的问题,因为 OP 使用 int 而不是 size_t 来表示大小)。
    【解决方案2】:

    您的代码中有多个问题:

    • mergeSort 函数将数组拆分为 2 个本地数组并递归调用自身对它们进行排序,但 merge 阶段不会将这些排序后的数组作为输入。您应该直接使用参数数组的一部分。
    • 大小计算很麻烦,许多调整会对size 的小值造成问题。使用一个简单的约定:传递end 作为数组末尾后第一个元素的偏移量。这样,只需从end 中减去start 即可计算大小。
    • merge 函数将k 初始化为0 而不是l

    这是一个更正的版本:

    void merge(int arr[], int start, int m, int end) {
        int size1 = m - start;
        int size2 = end - m;
        int arr1[size1];
        int arr2[size2];
        int i, j, k;
    
        for (i = 0; i < size1; i++) {
            arr1[i] = arr[start + i];
        }
        for (i = 0; i < size2; i++) {
            arr2[i] = arr[m + i];
        }
    
        i = j = 0;
        k = start;
    
        while (i < size1 && j < size2) {
            if (arr1[i] < arr2[j]) {
                arr[k++] = arr1[i++];
            } else {
                arr[k++] = arr2[j++];
            }
        }
        while (i < size1) {
            arr[k++] = arr1[i++];
        }
        while (j < size2) {
            arr[k++] = arr2[j++];
        }
    }
    
    void mergeSort(int *array, unsigned int size) {
        if (size >= 2) {
            int m = size / 2;
            mergeSort(array, m);
            mergeSort(array + m, size - m);
            merge(array, 0, m, size);
    }
    

    Bob__ 建议进行简化,仅将数组的前半部分保存到 arr1 并删除对 arr2 的需要。这是一个修改版本,还删除了始终为0start 和其他一些简化:

    void merge(int arr[], size_t m, size_t size) {
        int arr1[m];
        size_t i, j, k;
    
        for (i = j = k = 0; j < m; j++) {
            arr1[j] = arr[j];
        }
    
        while (i < m && j < size) {
            if (arr1[i] < arr[j]) {
                arr[k++] = arr1[i++];
            } else {
                arr[k++] = arr[j++];
            }
        }
        while (i < m) {
            arr[k++] = arr1[i++];
        }
    }
    
    void mergeSort(int *array, size_t size) {
        if (size >= 2) {
            size_t m = size / 2;
            mergeSort(array, m);
            mergeSort(array + m, size - m);
            merge(array, m, size);
    }
    

    但是请注意,使用自动存储分配arr1,也就是在堆栈上,可能会导致大型数组的未定义行为,通常大于几十万个元素。从堆中分配一个临时数组可以避免这个问题,但代价是额外的开销和分配失败的可能性。

    【讨论】:

    • 它可能更模糊或更不“对称”,但无需使用arr2 并复制后半部分,这些元素可以在需要时安全地移动到正确的位置。跨度>
    • @Bob__ 确实可以删除arr2,但必须调整m 的计算以确保size1 &gt;= size2 否则某些病理分布将被破坏。 int m = (size + 1) / 2; 成功了。
    • 我不确定size1 必须大于size2。我们称它们为 pq。我们强制执行 p q p + 1 以获得对数复杂度,但 p 可以更小,甚至 1(这就像冒泡排序)。当第一个 p (排序的)元素被复制时,相同数量的空间被释放。如果后半部分的元素移动到正确位置,则可用位置的总数保持不变(p 的实际值)。只有当其他元素之一被移回时,可用空间才会减少(因此元素 p 的数量仍将被复制)。顺便说一句,} 不见了。
    • @Bob__:确实你是对的,我不知道我从哪里得到这个想法,甚至size 是如何拆分的都没关系。 size / 2(size + 1) / 2 产生相同数量的递归调用,但 merge 适用于任何比例,尽管其他比例会降低整体性能。
    【解决方案3】:

    每个递归的排序合并是在堆栈上创建的本地数组上完成的,它不会转发到前一个函数调用。整个递归调用的最终结果就是{10,9,8,7,6}{5,4,3,2,1}的合并。

    可以找到调试验证here

    注意:输出在我的调试代码中有所不同,因为 mergeSort 的调用者可以有不同的实现

    更新sn-p如下,

    #include <stdio.h>
    void merge(int arr[], int l, int m, int r) {
    
        int size1 = m - l;
        int size2 = r - m;
    
        int arr1[size1];
        int arr2[size2];
    
        int i;
    
        for ( i = 0; i < m; i++ ) {
            arr1[i] = arr[l + i];
        }
    
        for ( i = 0; i+m < r; i++ ) {
            arr2[i] = arr[m + i ];
        }
    
        i = 0;
        int j = 0;
        int k = 0;
    
        while ( i < size1 && j < size2 ) {
            if ( arr1[i] < arr2[j] ) {
                arr[k++] = arr1[i++];
            } else {
                arr[k++] = arr2[j++];
                }
        }
        while ( i < size1 ) {
            arr[k++] = arr1[i++];
        }
        while ( j < size2 ) {
            arr[k++] = arr2[j++];
        }
    }
    
    void mergeSort(int* array, unsigned int size) {
    
        if ( size < 2 ) {
            return;
        }
    
        int start = 0;
        int middle = (size / 2);
        int end = size;
    
        mergeSort(array, middle);
        mergeSort(&array[middle], end-middle);
        merge(array, start, middle, end);
    }
    
    
    int main()
    {
        int a[] = {10,9,8,7,6,5,4,3,2,1};
        const int size = sizeof(a)/sizeof(a[0]);
        mergeSort(a,size);
        for(int i=0;i<size;i++)
           printf("%d ", a[i]);
    }
    

    输出:1 2 3 4 5 6 7 8 9 10

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-01-21
      • 1970-01-01
      • 2019-09-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-06
      • 2019-01-22
      相关资源
      最近更新 更多