C:合并 - 对具有不均匀元素数的数组进行排序

时间:2016-11-25 01:09:47

标签: c recursion merge segmentation-fault mergesort

我一直在为我的程序编程类做一个作业,我们为它提供了一个不能完全运行的合并排序程序。它对具有偶数个整数的数组执行合并排序,但会抛出具有奇数个整数的分段错误。

我理解排序是如何工作的,并且因为奇数导致分段错误而导致分段错误,因为数组以某种方式被过度填充。我也理解该解决方案将涉及原始数组是偶数还是奇数的测试,然后根据这个不同地将值传递给合并函数。尽管我对这个项目有所了解,但是我一直在试图让这个项目正常运行,并且我希望有人可以给我一些建议。

在发布之前我已经做了很多寻找答案的答案,但所有其他的例子都涉及到结构的合并排序程序,这超出了我迄今为止学到的内容。您将在我在下面发布的代码中看到。此外,完整的程序涉及一些其他文件,但我只包括mergesort.c文件和merge.c文件,正如我的教授所保证的那样,这是唯一的地方。需要制作。 main文件工作正常,只负责填充数组并调用mergesort函数。如果其他文件是必要的,请告诉我,我会发布它们。我没有的唯一原因是因为我们使用的是Linux shell,而且我还没有找到一种将shell中的代码复制并粘贴到我自己的操作系统的实用方法,并且需要一段时间才能写出来。< / p>

提前感谢您提供的任何指示。这是代码。

mergesort.c

#include <"mergesort.h">

void mergesort(int key[], int n) //key is the array, n is the size of key
{
    int j, k, m, *w;

    w = calloc(n, sizeof(int));
    assert(w != NULL);

    for (k = 1; k < n; k *= 2) {
        for (j = 0; j < n - k; j += 2 * k) {
            merge(key + j, key + j + k, w + j, k, k);
        }
        for (j = 0; j < n; ++j) {
            key[j] = w[j];
        }   
    }
    free(w);
}

merge.c

#include "mergesort.h"

void merge(int a[], int b[], int c[], int m, int n) {
    int i = 0, j = 0, k = 0;

    while (i < m && j < n) {
        if (a[i] < b[j]) {
            c[k++] = a[i++];
        } else {
            c[k++] = b[j++];
        }   
    }

    while (i < m) {
        c[k++] = a[i++];
    }
    while (j < n) {
        c[k++] = b[j++];
    }   
}

2 个答案:

答案 0 :(得分:3)

您的代码存在一些问题:

  • 包含预处理程序指令不正确,使用#include "mergesort.h"#include <mergesort.h>

  • 您必须正确计算传递给merge()的数组的大小,以便它不会读取超出最后一个块的末尾。按照当前编码,n必须是2的强大功能,以避免未定义的行为。

以下是mergesort.c的更正版本,用于您的目的:

#include "mergesort.h"

void mergesort(int key[], int n) {
    // key is the array, n is the number of elements
    int i, j, k, m;
    int *w;

    // allocate the working array
    w = calloc(n, sizeof(int));
    // abort the program on allocation failure
    assert(w != NULL);

    // for pairs of chunks of increasing sizes
    for (k = 1; k < n; k *= 2) {
        // as long as there are enough elements for a pair
        for (j = 0; j + k < n; j = j + k + m) {
            // compute the size of the second chunk: default to k
            m = k;
            if (j + k + m > n) {
                // chunk is the last one, size may be smaller than k
                m = n - j - k;
            }
            // merge adjacent chunks into the working array
            merge(key + j, key + j + k, w + j, k, m);
            // copy the resulting sorted list back to the key array
            for (i = 0; i < k + m; i++) {
                key[j + i] = w[j + i];
            }
        }
    }
    free(w);
}

以下是有关此练习的一些其他说明,但您可能不够先进,可能不允许更改API:

  • 使用2个不同的源文件似乎有点过分。 merge例程是一个值得static的辅助函数。它将由现代编译器内联扩展。

  • 数组大小应该在相应指针之后传递为size_t(为了保持一致)。

  • 您应该返回失败代码并让调用者处理程序正常失败,而不是断言分配成功。

  • 您可以使用工作数组的开头进行所有合并操作。这提高了缓存效率。

以下是包含所有这些更改的版本:

#include "mergesort.h"

static void merge(int a[], size_t m, int b[], size_t n, int c[]) {
    size_t i = 0, j = 0, k = 0;

    while (i < m && j < n) {
        if (a[i] < b[j]) {
            c[k++] = a[i++];
        } else {
            c[k++] = b[j++];
        }
    }
    while (i < m) {
        c[k++] = a[i++];
    }
    while (j < n) {
        c[k++] = b[j++];
    }
}

int mergesort(int key[], size_t n) { 
    // key is the array, n is the size of key
    // return 0 for success, -1 for failure with error code in errno
    size_t i, j, k, m;
    int *w;

    w = calloc(n, sizeof(int));
    if (w == NULL)
        return -1;

    for (k = 1; k < n; k *= 2) {
        for (j = 0; j + k < n; j += k + m) {
            m = k;
            if (j + k + m > n) {
                m = n - j - k;
            }
            merge(key + j, k, key + j + k, m, w + j);
            // copy the sorted chunk back to the key array
            for (i = 0; i < k + m; i++) {
                key[j + i] = w[i];
            }
        }
    }
    free(w);
    return 0;
}

您可以通过删除功能merge()中索引变量的近一半测试来进一步改进实现:

static void merge(int a[], size_t m, int b[], size_t n, int c[]) {
    /* always called with m > 0 and n > 0 */
    for (size_t i = 0, j = 0, k = 0;;) {
        if (a[i] < b[j]) {
            c[k++] = a[i++];
            if (i == m) {
                while (j < n) {
                    c[k++] = b[j++];
                }
                break;
            }
        } else {
            c[k++] = b[j++];
            if (j == n) {
                while (i < m) {
                    c[k++] = a[i++];
                }
                break;
            }
        }
    }
}

您可以通过以下进一步的想法改进mergesortmerge

  • 比较a的最后一个元素和bmerge的第一个元素,可以对部分或完全排序的数组进行大幅度的改进。

  • merge可以返回要复制的元素数量,删除已排序的案例中的所有复制。

  • 通过将左块复制到临时数组并合并到key数组中,可以减小临时数组的大小。

  • 合并平衡块大小而不是2的幂会减少2个数组大小的非幂的比较总数,但使用递归方法更容易实现。

答案 1 :(得分:0)

所以我找到了你的分段错误来自哪里。如果仔细查看mergesort中的第一个内部for循环:

        for(j = 0; j < n - k; j += 2 * k)
        {
            merge(key + j, key + j + k, w + j, k, k);
        }  

你会注意到这个条件与你给合并函数的内容并不完全一致,因为它是你的数组切片的边界。条件为j < n - k,因此j的最大值为n - k - 1。但是在合并的参数中,您传递的第二个数组切片从key + j + k开始,并且您告诉它的大小为k,因此如果您替换{j + k + k - 1,则最终为索引j {1}}按其最大值n - k - 1 + k + k - 1 = n。这意味着你要告诉合并函数他可以去索引n。由于key的大小为n,因此它没有索引n。那你怎么改写你的病情呢?我们刚刚计算了合并将访问的最大索引:j + k + k - 1。所以这意味着你必须将j + k + k - 1 < n设置为条件。这意味着:

        for(j = 0; j <= n - (k*2); j += 2 * k)
        {
            merge(key + j, key + j + k, w + j, k, k);
        } 

现在我们摆脱了分段错误,我们可以进入第二部分:使其适用于所有规模。之所以它只适用于2的幂(甚至不是所有偶数尺寸:尝试排序这[2,3,5,6,4,1]你会看到的)是因为你的{{1 }}。 k设置确定将在循环中合并的切片的大小。每轮后k乘以2,所以它只会获得2的幂!当它不是2的幂时,它会忽略那些“超过”2的幂的部分...如果你理解我的意思?在我们做出解决分段错误的更改之前,它会尝试执行此操作但由于这个原因而失败(并返回错误)。 我们现在要做的就是让它排除他忽略的最后一片。我只会复制mergesort-function,因为它是唯一会改变的东西:

k

另外,我的编译器抱怨你没有使用的变量:void mergesort(int key[], int n) //key is the array, n is the size of key { int j, k, neglected, *w; w = calloc(n, sizeof(int)); assert(w != NULL); for(k = 1; k < n; k *= 2){ for(j = 0; j <= n - (k*2); j += 2 * k){ merge(key + j, key + j + k, w + j, k, k); } //size of part that got neglected (if it could fully be divided in slices of 2*k, this will be 0) neglected = n % (2*k); //copy everything except the neglected part (if there was none, it will copy everything) for(j = 0; j < n-neglected; ++j) { key[j] = w[j]; } if(neglected != 0 && neglected < n){ //couldn't devide it fully in slices of 2*k ==> the last elements were left out! merge them together with the last merged slice merge(key + n - (2*k) - neglected, key + n-neglected, w + n - (2*k) - neglected, 2*k, neglected); for(j = n - (2*k) - neglected; j < n; ++j) { //copy the part we just merged key[j] = w[j]; } } for(j = 0; j < n; ++j) { key[j] = w[j]; } } free(w); }