Arrays 如何在不使用其他数组分割初始数组的情况下进行合并排序?

Arrays 如何在不使用其他数组分割初始数组的情况下进行合并排序?,arrays,c,sorting,mergesort,Arrays,C,Sorting,Mergesort,我试图解决一个问题,该问题要求编写合并排序代码,但不使用额外的数组对初始数组进行分区。我想写的代码差不多不错,但我面临的问题是,在排序时,我不知道如何维护和更新数组。我知道问题出在合并函数中 如何修复代码 #include <stdio.h> #include <stdlib.h> #include <math.h> void PrintArray(int A[], int n) { for(int i=0; i < n; i++)

我试图解决一个问题,该问题要求编写合并排序代码,但不使用额外的数组对初始数组进行分区。我想写的代码差不多不错,但我面临的问题是,在排序时,我不知道如何维护和更新数组。我知道问题出在合并函数中

如何修复代码

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

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

void merge(int A[], int left, int mid, int right, int n){
  
int B[n];

int i = left, j = mid+1, k=0;

while(i<=mid && j <= right){

  if(A[i]>=A[j]){
    B[k++] = A[i++];
  }

  else {
    B[k++] = A[j++];
  }

}

while(i<=mid){
  B[k++] = A[i++];
}

while(j<=right){
  B[k++] = A[j++];
}

for(i=0; i<n; i++){
  A[i] = B[i];
}

}

void MergeSort(int A[], int left, int right, int n)
{ 
  if(left<right){
    int mid;
    mid = floor((left+right)/2);
    MergeSort(A,left,mid,n/2);
    MergeSort(A,mid+1,right,n/2);
    merge(A,left,mid,right,n);
  }

  else return;
}

int main()
{
    int n;
    scanf("%d",&n);

    int A[n];

    for(int i=0; i < n; i++) scanf("%d", &A[i]);
        
    MergeSort(A, 0, n-1, n);
    PrintArray(A, n);
    return 0;
}
在合并中的最终for循环中,更改:

进入:

编辑:即使在修复之后,排序仍然是错误的。最终循环的正确修复方法是:

for (i = left;  i <= right;  ++i)
    A[i] = B[i - left];
在合并中的最终for循环中,更改:

进入:

编辑:即使在修复之后,排序仍然是错误的。最终循环的正确修复方法是:

for (i = left;  i <= right;  ++i)
    A[i] = B[i - left];

有一些合并排序变体不使用除局部变量以外的任何额外空间。这种方法的最佳实现比较复杂,比传统的合并排序慢50%,而且大多数实现都是用于学术研究的

有一篇关于一种变体的wiki文章,它是插入排序和合并排序的混合

链接到此github存储库中grailsort.h中的更优化版本。void GrailSortSORT_TYPE*arr,int Len函数不使用任何额外的缓冲区


有一些合并排序变体不使用除局部变量以外的任何额外空间。这种方法的最佳实现比较复杂,比传统的合并排序慢50%,而且大多数实现都是用于学术研究的

有一篇关于一种变体的wiki文章,它是插入排序和合并排序的混合

链接到此github存储库中grailsort.h中的更优化版本。void GrailSortSORT_TYPE*arr,int Len函数不使用任何额外的缓冲区


给出了一组只使用源和目标两个数组的算法。或者您正在寻找一种就地算法?您希望如何在单个数组中对任何内容进行分区?如果将数组大小增加一倍并使用偏移量,您可以这样做,但这与正确地使用单独的数组进行分区没有什么不同。@DavidC.Rankin我想我们不需要实际地对数组进行分区。我们可以使用变量left、right和mid对其进行虚拟分区,因为我们可以通过将第一个数组从左到中,然后将另一个数组从mid+1到右,从一个数组中创建两个数组。@EugeneSh。我认为使用单个额外数组作为临时存储进行排序,然后将其复制回初始数组将被视为一种就地算法,但正如Craig提到的,这种方法有时会导致堆栈溢出,并不是真正的就地方法,因此我猜我的方法不准确。但我这样做是因为问题明确地说我们不能为分区创建2个额外的数组,并提供了一个代码结构来填充,这就是为什么我们甚至不能创建更多类似于Wikipedia的函数solution@SuryanshManav您有多个数组。您在main中声明了VLA A,在merge中声明了B。由于您从MergeSort递归调用merge,因此在每个递归级别上再创建3个vla。说到底,您可能已经使用了几十个阵列。给出了一组只使用源和目标两个数组的算法。或者您正在寻找一种就地算法?您希望如何在单个数组中对任何内容进行分区?如果将数组大小增加一倍并使用偏移量,您可以这样做,但这与正确地使用单独的数组进行分区没有什么不同。@DavidC.Rankin我想我们不需要实际地对数组进行分区。我们可以使用变量left、right和mid对其进行虚拟分区,因为我们可以通过将第一个数组从左到中,然后将另一个数组从mid+1到右,从一个数组中创建两个数组。@EugeneSh。我认为使用单个额外数组作为临时存储进行排序,然后将其复制回初始数组将被视为一种就地算法,但正如Craig提到的,这种方法有时会导致堆栈溢出,并不是真正的就地方法,因此我猜我的方法不准确。但我这样做是因为问题明确地说我们不能为分区创建2个额外的数组,并提供了一个代码结构来填充,这就是为什么我们甚至不能创建更多类似于Wikipedia的函数solution@SuryanshManav您有多个数组。您在main中声明了VLA A,在merge中声明了B。由于您从MergeSort递归调用merge,因此在每个递归级别上再创建3个vla。你可能有几十个阵列在使用的时候,所有的说和做。非常感谢克雷格为您的评论和更正代码。我根据您的建议更新了for循环,现在它通过了所有测试结果。但是我想我们仍然需要函数中的n来声明B数组,否则我们怎么知道呢
尺寸是多少?此外,只允许使用Merge和MergeSort函数,因为问题给出的代码模板中已经包含了这些参数。是的,我是按降序排序的,因为问题是这样问的。老实说,我不太明白就地算法的意思,所以我认为这种方法是就地算法,但正如你提到的,它可以导致大输入的溢出错误,我现在有了一个想法。还有,我现在明白了。使用floor进行整数计算一点用处都没有,也许我想要的是ceil LOL,但我很高兴它在按照您的建议更新for循环后起到了作用。我很长一段时间都在做这件事,当我把它贴在这里的时候,我完全不知所措。再次感谢你的时间和努力;此方法使用与原始数组大小相同的附加数组。非常感谢Craig提供的评论和更正代码。我根据您的建议更新了for循环,现在它通过了所有测试结果。但是我想我们仍然需要函数中的n来声明B数组,否则我们怎么知道大小呢?此外,只允许使用Merge和MergeSort函数,因为问题给出的代码模板中已经包含了这些参数。是的,我是按降序排序的,因为问题是这样问的。老实说,我不太明白就地算法的意思,所以我认为这种方法是就地算法,但正如你提到的,它可以导致大输入的溢出错误,我现在有了一个想法。还有,我现在明白了。使用floor进行整数计算一点用处都没有,也许我想要的是ceil LOL,但我很高兴它在按照您的建议更新for循环后起到了作用。我很长一段时间都在做这件事,当我把它贴在这里的时候,我完全不知所措。再次感谢你的时间和努力;此方法使用与原始数组大小相同的附加数组。
for (i = left;  i <= right;  ++i)
    A[i] = B[i - left];
#include <stdio.h>
#include <stdlib.h>
#include <math.h>

void
PrintArray(int A[], int n)
{
    int totlen = 0;

    for (int i = 0; i < n; i++) {
        totlen += printf(" %d", A[i]);
        if (totlen >= 72) {
            printf("\n");
            totlen = 0;
        }
    }

    if (totlen > 0)
        printf("\n");
}

void
merge(int A[], int left, int mid, int right, int *B)
{

    int i = left,
        j = mid + 1,
        k = 0;

    int Ai = A[i];
    int Aj = A[j];

    while (i <= mid && j <= right) {
        if (Ai <= Aj) {
            B[k++] = Ai;
            Ai = A[++i];
        }
        else {
            B[k++] = Aj;
            Aj = A[++j];
        }
    }

    while (i <= mid)
        B[k++] = A[i++];

    while (j <= right)
        B[k++] = A[j++];

    // original code
#if 0
    for (i = 0; i < n; i++)
        A[i] = B[i];
#endif

    // first fix -- still broken
#if 0
    for (i = 0; i < n; i++)
        A[left + i] = B[i];
#endif

    // correct fix
#if 1
    for (i = left;  i <= right;  ++i)
        A[i] = B[i - left];
#endif
}

void
MergeSort(int A[], int left, int right, int *B)
{
    if (left < right) {
        int mid = (left + right) / 2;
        MergeSort(A, left, mid, B);
        MergeSort(A, mid + 1, right, B);
        merge(A, left, mid, right, B);
    }
}

void
MergeSortPub(int A[], int n)
{
    int *B = malloc(sizeof(*B) * n);

    MergeSort(A,0,n - 1,B);

    free(B);
}

void
dotest(int tstno)
{

    int n = rand() % 1000;

    int *A = malloc(sizeof(*A) * n);

    for (int i = 0;  i < n;  ++i)
        A[i] = n - i;

    MergeSortPub(A,n);

    int old = A[0];
    int bad = 0;
    for (int i = 1;  i < n;  ++i) {
        int cur = A[i];
        if (cur < old) {
            if (! bad)
                printf("dotest: %d -- i=%d old=%d cur=%d\n",tstno,i,old,cur);
            bad = 1;
        }
        old = cur;
    }

    if (bad) {
        PrintArray(A,n);
        exit(1);
    }
}

int
main(void)
{
    int n;

#if 0
    scanf("%d", &n);

    int A[n];

    for (int i = 0; i < n; i++)
        scanf("%d", &A[i]);

    MergeSortPub(A, n);
    PrintArray(A, n);
#else
    for (int tstno = 1;  tstno <= 1000;  ++tstno)
        dotest(tstno);
#endif

    return 0;
}