简体   繁体   English

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

[英]C: Merge-Sort of an array with uneven number of elements

I've been working on an assignment for my Procedural Programming class where we are provided with a merge-sort program that does not function completely. 我一直在为我的程序编程类工作,我们提供了一个不能完全运行的合并排序程序。 It performs a merge-sort on arrays with an even number of integers, but throws a segmentation fault with an odd number of integers. 它对具有偶数个整数的数组执行合并排序,但会抛出具有奇数个整数的分段错误。

I understand how the sorting works, and that the segmentation fault is being thrown because the odd number is causing the segmentation fault because the array is being over-filled somehow. 我理解排序是如何工作的,并且由于奇数导致分段错误而导致分段错误,因为数组以某种方式被过度填充。 I also understand that the solution is going to involve a test for whether or not the original array is even or odd, and then pass the values to the merge function differently depending on this. 我也理解该解决方案将涉及原始数组是偶数还是奇数的测试,然后根据这个不同地将值传递给合并函数。 Despite what I do understand about the program, I have been banging my head against the wall for weeks trying to get this to work properly, and I'm hoping someone can give me some advice. 尽管我对这个项目有所了解,但是我一直在试图让这个项目正常运行,并且我希望有人可以给我一些建议。

I've done a lot of looking around for answers before posting this, but all other examples involve merge-sort programs with structs, which is beyond what I've learned so far. 在发布之前我已经做了很多寻找答案的答案,但是所有其他的例子都涉及到结构的合并排序程序,这超出了我迄今为止学到的内容。 You'll see in the code I post below. 您将在我在下面发布的代码中看到。 Also, the full program involves a few other files, but I've included just the mergesort.c file and the merge.c file which, as I've been assured by my professor, are the only places any changes need to be made. 此外,完整的程序涉及一些其他文件,但我只包括mergesort.c文件和merge.c文件,正如我的教授所保证的那样,这是唯一需要进行任何更改的地方。 The main file works perfectly and is only responsible for filling the array and calling the mergesort function. main文件工作正常,只负责填充数组并调用mergesort函数。 If the other files are necessary, let me know and I'll post them. 如果其他文件是必要的,请告诉我,我会发布它们。 The only reason I haven't is because we are using a Linux shell, and I haven't found a practical way to copy and paste code from the shell to my own operating system, and it takes a while to write it out. 我没有的唯一原因是因为我们使用的是Linux shell,而且我还没有找到一种将shell中的代码复制并粘贴到我自己的操作系统的实用方法,并且需要一段时间才能将其写出来。

Thanks in advance for any pointers you can provide. 提前感谢您提供的任何指示。 Here is the code. 这是代码。

mergesort.c 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 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++];
    }   
}

Your code has some problems: 您的代码有一些问题:

  • The include preprocessor directive is incorrect, either use #include "mergesort.h" or #include <mergesort.h> . include预处理程序指令不正确,使用#include "mergesort.h"#include <mergesort.h>

  • You must compute the size of the arrays passed to merge() correctly so it does not read beyond the end of the last chunk. 您必须正确计算传递给merge()的数组的大小,以便它不会读取超出最后一个块的末尾。 As currently coded, n must be a power of 2 to avoid undefined behavior. 按照当前编码, n必须是2的幂才能避免未定义的行为。

Here is a corrected version of mergesort.c for your purpose: 以下是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);
}

Here are some additional remarks about this exercise, but you might not be advanced enough and changing the API is probably not allowed: 以下是有关此练习的一些其他说明,但您可能不够先进,可能不允许更改API:

  • Using 2 different source files seems overkill. 使用2个不同的源文件似乎有点矫枉过正。 The merge routine is an auxiliary function that deserves to be static . merge例程是一个值得static的辅助函数。 It will be expanded inline by modern compilers. 它将由现代编译器内联扩展。

  • Array sizes should be passed as size_t just after the corresponding pointer (for consistency). 数组大小应该在相应指针之后作为size_t传递(为了保持一致性)。

  • Instead of asserting the allocation success, you should return a failure code and let the caller handler the failure gracefully. 您应该返回失败代码并让调用者处理程序优雅地失败,而不是断言分配成功。

  • You can use the start of the working array for all merge operations. 您可以使用工作数组的开头进行所有合并操作。 This improves cache efficiency. 这提高了缓存效率。

Here is a version with all these changes: 这是一个包含所有这些更改的版本:

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

You can further improve the implementation by removing almost half the tests on the index variables in function merge() : 您可以通过在函数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;
            }
        }
    }
}

You can improve mergesort and merge with these further ideas: 您可以改进mergesort并与这些进一步的想法merge

  • comparing the last element of a and the first element of b in merge allows vast speed improvements on partially or totally sorted arrays. 比较的最后一个元素a和的第一个元素bmerge允许在部分或完全排序阵列广阔速度的改进。

  • merge could return the number of elements to copy back, removing all copying in the sorted case. merge可以返回要复制的元素数,删除已排序的案例中的所有复制。

  • by copying the left chunk to the temporary array and merging into the key array, you can reduce the size of the temporary array. 通过将左块复制到临时阵列并合并到key阵列中,可以减小临时阵列的大小。

  • merging balanced chunk sizes instead of powers of 2 reduces the total number of comparisons for non power of 2 array sizes, but it is easier to implement with a recursive approach. 合并平衡块大小而不是2的幂会减少2个阵列大小的非幂的比较总数,但使用递归方法更容易实现。

So I've found where your the segmentation error is comming from. 所以我找到了你的分段错误来自哪里。 If you take a closer look to the first inner for-loop in your mergesort: 如果仔细查看mergesort中的第一个内部for循环:

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

you'll notice that the condition does not really coincide with what you are giving to the merge function as boundaries for your slices of the array. 你会注意到这个条件与你给合并函数的内容并不完全一致,作为数组切片的边界。 The condition is j < n - k so the max value of j is n - k - 1 . 条件是j < n - k因此j的最大值是n - k - 1 But in the arguments of your merge the second array-slice you pass starts at key + j + k and you tell it has size k , so you end up at index j + k + k - 1 , if you replace your j by its maximum value you get n - k - 1 + k + k - 1 = n . 但是在你的合并的参数中,你传递的第二个数组切片从key + j + k ,你告诉它有大小为k ,所以你最终在索引j + k + k - 1 ,如果你用它替换你的j得到的最大值n - k - 1 + k + k - 1 = n Which means that you are telling the merge-function he can go till index n . 这意味着你告诉他可以去指数n的合并函数。 Since the size of key is n, it has no index n. 由于key的大小为n,因此它没有索引n。 So how do you have to rewrite your condition? 那你怎么改写你的病情呢? We just calculated the maximum index the merge will be accessing: j + k + k - 1 . 我们刚刚计算了合并将访问的最大索引: j + k + k - 1 So it means you just have to set j + k + k - 1 < n as condition. 所以这意味着你必须设置j + k + k - 1 < n作为条件。 this means: 这意味着:

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

Now we got rid of the segmentation faults, we can go to the second part: making it work for all sizes. 现在我们摆脱了分段错误,我们可以进入第二部分:使其适用于所有规模。 The reason why it does only work for sizes wich are a power of 2 (not even all even sizes: try sorting this [2, 3, 5, 6, 4, 1] you'll see) is because of your k . 之所以它只适用于2的力量(甚至不是所有偶数尺寸:尝试排序你会看到的[2,3,5,6,4,1])是因为你的k It is k that sets the determines the size of the slices that will be merged in the loop. k是设置确定将在循环中合并的切片的大小。 k gets multiplied by 2 after each round, so it will only gets sizes that are a power of 2! k在每一轮之后乘以2,所以它只会得到2的幂! When it's not a power of 2, it will just ignore the part that gets "over" the power of 2...if you get what I mean? 当它不是2的幂时,它会忽略那些“超过”2的幂的部分...如果你理解我的意思? Before we made that change that solved the segmentation error it would have just tried to do it but fail for that reason (and return an error). 在我们做出解决分段错误的更改之前,它会尝试执行此操作但由于这个原因而失败(并返回错误)。 The thing we have to do now is to make it sort that last slice he just ignores. 我们现在要做的就是让它排除他忽略的最后一片。 I'll only copy the mergesort-function as it is the only thing that will change: 我只会复制mergesort-function,因为它是唯一会改变的东西:

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

Also, my compiler was complaining about a variable you weren't using: m 此外,我的编译器抱怨你没有使用的变量: m

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM