【问题标题】:Merge-sort implementation doesn't work合并排序实现不起作用
【发布时间】:2015-10-04 17:32:23
【问题描述】:

我正在尝试使用数组在 C 中实现归并排序,这是我的代码:

#include <stdio.h>
#include <stdlib.h>

void merge(int s[], int low, int middle, int high)
{
    int i,l=0,r=0;
    int left[high/2], right[high/2];

    for(i = low; i<=middle; i++) left[i-low] = s[i];
    for(i = middle+1; i<=high; i++) right[i-middle-1] = s[i];

    i = low;
    while(l <= middle-low || r <= high - middle - 1)
    {
        if(left[l] <= right[r])
        {
            s[i++] = left[l];
            l++;
        }
        else
        {
            s[i++] = right[r];
            r++;
        }
    }
    while(l <= middle-low)
    {
        s[i++] = left[l];
        l++;
    }
    while(r <= high - middle - 1)
    {
        s[i++] = left[r];
        r++;
    }
}

void mergesort(int s[], int low, int high)
{
    int i;
    int middle;
    if(low < high){
        middle = (low + high)/2;
        mergesort(s, low, middle);
        mergesort(s, middle+1, high);
        merge(s, low, middle, high);
    }
}

int main()
{
    int nums[] = {5, 345, 1, 120, 40, 3450};
    int size = (sizeof(nums))/(sizeof(int));
    int i;
    for(i = 0; i < size; i++)
        printf("%d ", nums[i]);
    printf("\n");
    mergesort(nums, 0, size);
    for(i = 0; i < size; i++)
        printf("%d ", nums[i]);
    printf("\n");
    return 0;
}

输出:

5 345 1 120 40 3450 
0 1 4 5 40 120 

这有点接近。有人可以指出我的错误吗?谢谢。

【问题讨论】:

  • 你的合并函数有可疑的 for 循环上限,&lt;= 肯定是错误的。
  • 乍一看:您的数组上限是排他性的,这在 C 中很常见。这意味着所有 &lt;= 作为循环条件可能应该只是 &lt;。当high 为奇数时,您的右子数组也将少一个元素。
  • 将所有
  • 这就是为什么你真的应该在c中使用指针算术。您可以通过每次将要排序的子数组的开头传递给mergesort() 来更清楚地表达这一点,而不是捏造偏移量。此外,在堆栈上分配临时数组不会扩展到要排序的更大集合。
  • cmets 并没有说当您更改条件时您的代码将起作用。他们要求您进一步研究这些条件并检查您的代码。这些更改也不会破坏代码;代码已经坏了。

标签: c arrays sorting mergesort


【解决方案1】:

您在多个地方越界访问数组。您的代码使用 C 风格的范围,它具有包含的下限 L 和独占的上限 H。独占意味着上限H 不是(子)数组中的有效索引。范围内的典型循环如下所示:

for (i = L; i < U; i++) ...

i = L;
while (i < U) ...

在这样的循环中,大于或等于运算符&lt;= 应该让你保持警惕,1 的加法或减法也应该如此。在某些情况下它们可能是正确的,但它们通常是数组索引不一致的结果。

让我们根据 C 风格的范围来修改您的代码:

int left[high/2], right[high/2];

数组大小错误。左边的数组有middle - low 元素,右边的数组有high - middle 元素。如果数组大小high - low 是奇数,则右侧比左侧多一个元素。

for(i = low; i<=middle; i++) left[i-low] = s[i];

您错误地将中间元素放在左侧数组中。它是右数组的第一个元素。

for(i = middle+1; i<=high; i++) right[i-middle-1] = s[i];

这里也一样,另外您可以访问数组之外​​的s[high]

i = low;
while(l <= middle-low || r <= high - middle - 1)

条件应该有&lt;,没有-1。更重要的是,条件都应该为真,否则访问子数组越界;因此运算符应该是´&&`。

    if(left[l] <= right[r])

&lt;= 没问题,不过一次。

while(l <= middle-low)
{
    s[i++] = left[l];
    l++;
}
while(r <= high - middle - 1)
{
    s[i++] = left[r];
    r++;
}

这里,应该又是&lt;。另请注意,您使用索引r 访问left,这可能只是由于复制和粘贴造成的拼写错误。

if(low < high){
    middle = (low + high)/2;
    mergesort(s, low, middle);
    mergesort(s, middle+1, high);
    merge(s, low, middle, high);
}

这里,对 megesort 的第二次调用应该是 middle,而不是 middle + 1。因为上界是互斥的,下界不是,所以相邻数组共享相同的边界。

这是一种有效的方法:

void merge(int s[], int low, int middle, int high)
{
    int i, l = 0, r = 0;
    int left[middle - low];
    int right[high - middle];

    for (i = low; i < middle; i++) left[i - low] = s[i];
    for (i = middle; i < high; i++) right[i - middle] = s[i];

    i = low;
    while (low + l < middle && middle + r < high) {
        if (left[l] < right[r]) {
            s[i++] = left[l];
            l++;
        } else {
            s[i++] = right[r];
            r++;
        }
    }

    while (low + l < middle) {
        s[i++] = left[l];
        l++;
    }

    while (middle + r < high) {
        s[i++] = right[r];
        r++;
    }
}

void mergesort(int s[], int low, int high)
{
    int middle;

    if (low + 1 < high) {
        middle = (low + high) / 2;
        mergesort(s, low, middle);
        mergesort(s, middle, high);
        merge(s, low, middle, high);
    }
}

代码仍然可以改进。左右子数组的不同索引使得代码难以维护和测试。如果您已经了解了指针算法,您可以完全不用 low 绑定,只需将 array + low 和大小作为新的数组基数传递,正如 EOF 在评论中所建议的那样。

【讨论】:

    【解决方案2】:

    M Oehm 在他的回答中提供了原始代码的解释和固定示例。

    这是一个替代版本,它一次性分配临时数组并使用一对共同递归函数来避免数据复制。我不确定为什么经常使用自上而下的归并排序,自下而上的归并排序是非递归的,速度更快,更易于理解。

    在我的系统 Intel 2600K 3.4ghz 上,这个例子可以在大约 2 秒内对 2000 万个 32 位整数进行排序。 (自下而上的归并排序大约需要 1.9 秒)。

    void TopDownSplitMergeAtoA(int a[], int b[], size_t ll, size_t ee);
    void TopDownSplitMergeAtoB(int a[], int b[], size_t ll, size_t ee);
    void MergeRuns(int a[], int b[], size_t ll, size_t rr, size_t ee);
    
    void TopDownMergeSort(int a[], size_t n)
    {
        int *b;
        if(n < 2)                           // if size < 2 return
            return;
        b = malloc(n * sizeof(int));        // one time allocation
        TopDownSplitMergeAtoA(a, b, 0, n);
        free(b);
        return;
    }
    
    void TopDownSplitMergeAtoA(int a[], int b[], size_t ll, size_t ee)
    {
    size_t rr;
        if((ee - ll) == 1)                  // if size == 1 return
            return;
        rr = (ll + ee)>>1;                  // midpoint, start of right half
        TopDownSplitMergeAtoB(a, b, ll, rr);
        TopDownSplitMergeAtoB(a, b, rr, ee);
        MergeRuns(b, a, ll, rr, ee);        // merge b to a
    }
    
    void TopDownSplitMergeAtoB(int a[], int b[], size_t ll, size_t ee)
    {
    size_t rr;
        if((ee - ll) == 1){                 // if size == 1 copy a to b
            b[ll] = a[ll];
            return;
        }
        rr = (ll + ee)>>1;                  // midpoint, start of right half
        TopDownSplitMergeAtoA(a, b, ll, rr);
        TopDownSplitMergeAtoA(a, b, rr, ee);
        MergeRuns(a, b, ll, rr, ee);        // merge a to b
    }
    
    void MergeRuns(int a[], int b[], size_t ll, size_t rr, size_t ee)
    {
        size_t o = ll;                      // b[]       index
        size_t l = ll;                      // a[] left  index
        size_t r = rr;                      // a[] right index
        while(1){                           // merge data
            if(a[l] <= a[r]){               // if a[l] <= a[r]
                b[o++] = a[l++];            //   copy a[l]
                if(l < rr)                  //   if not end of left run
                    continue;               //     continue (back to while)
                while(r < ee)               //   else copy rest of right run
                    b[o++] = a[r++];
                break;                      //     and return
            } else {                        // else a[l] > a[r]
                b[o++] = a[r++];            //   copy a[r]
                if(r < ee)                  //   if not end of right run
                    continue;               //     continue (back to while)
                while(l < rr)               //   else copy rest of left run
                    b[o++] = a[l++];
                break;                      //     and return
            }
        }
    }
    

    【讨论】:

    • 每次有人将函数参数声明为数组foo(type bar[])sizeof()-user 就会死掉。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-04-23
    • 2019-07-31
    • 2016-08-19
    • 1970-01-01
    • 2018-12-04
    相关资源
    最近更新 更多