【问题标题】:C++: Merge Sort Merging ArraysC++:合并排序合并数组
【发布时间】:2013-01-06 08:25:30
【问题描述】:

当我运行代码时,我会得到很多重复的数字和/或大的负数,当你把东西添加到数组中时通常会出现这些数字。我相信问题出在我自己进行合并时。

void mergeSort( int list[], int lb, int ub )
{
    int mid;
    if ( lb < ub )
    {
        mid = (lb + ub) /  2;
        mergeSort(list, lb, mid);
        mergeSort(list, mid + 1, ub);
        myMerge(list, lb, mid , ub);
    }
}

template <class M>
void myMerge( M list[], int lb, int mid, int ub )
{
   int i, j;
   int size1 = mid - lb + 1;
   int size2 = ub - mid;

    M* tmpArray1 = new M[size1 + 1];
    M* tmpArray2 = new M[size2 + 1];

    for( i=0; i<size1; i++ )
    {
        tmpArray1[i] = list[lb + i - 1];
    }

    for( j=0; j<size2; j++ )
    {
        tmpArray2[j] = list[mid + j];
    }

    tmpArray1[size1 + 1] = INT_MAX;
    tmpArray2[size2 + 1] = INT_MAX;

    i = 0;
    j = i;

    for( int k=lb; k<ub; k++ )
    {
        if ( tmpArray1[i] <= tmpArray2[j] )
        {
            list[k] = tmpArray1[i];
                i++;
        }
        else
        {
            list[k] = tmpArray2[j];
            j++;
        }
    }
}

这可能是一些愚蠢的问题,比如迭代问题......有什么想法吗?

【问题讨论】:

  • 尝试通过valgrind运行它;它会比我们更快地发现错误!
  • 你需要自己写这个吗(例如,为了家庭作业)?如果没有,请使用std::merge
  • [Aside] 您的合并算法正在疯狂地泄漏内存(准确地说,每次调用 mergesort 时都会泄漏 O(n lg n) 个单元格),因为您没有 delete[] 任何东西。跨度>
  • 是的,它用于家庭作业,不能使用 std::merge。我知道泄漏。谢谢你。
  • 很遗憾你不能use std::merge。它使编写起来非常容易。

标签: c++ arrays sorting merge


【解决方案1】:

您的算法存在一些问题。

首先,它会导致内存泄漏,因为它分配了永远不会删除的数组。需要几个delete[] 指令来解决这个问题。

其次,存在索引错误:某些索引变为负数,这是您肯定不希望的(例如,当您执行tmpArray1[i] = list[lb + i - 1]; 时,因为lbi 都可以为0) .

第三,你缺少一个基本步骤:你永远不会交换两个元素的值。您的递归步骤看起来不错,但递归必须在某个点结束并做一些具体的事情(即当您的范围仅跨越 2 个元素时)。您的 mergeSort() 函数拆分范围并仅递归调用第一个和第二个子范围,但在递归结束时对它们不执行任何操作。

第四,您没有正确处理两个子范围具有不同大小的情况(一个子范围可能比另一个大一个一个元素)。

您应该如何修复您的代码(在 GCC 4.7.2 上测试):

template <class M>
void myMerge( M arr[], int lb, int mid, int ub )
{
   int i, j;
   int size1 = mid - lb + 1;
   int size2 = ub - mid;

    M* tmpArray1 = new M[size1];
    M* tmpArray2 = new M[size2];

    for( i=0; i<size1; i++ )
    {
        tmpArray1[i] = arr[lb + i]; // THIS NEEDED FIXING
    }

    for( j=0; j<size2; j++ )
    {
        tmpArray2[j] = arr[mid + 1 + j]; // THIS NEEDED FIXING AS WELL (add +1...)
    }

    i = 0;
    j = i;

    for( int k=lb; k<=ub; k++ )
    {
        if (i == size1) // HANDLES THE CASE WHEN FIRST RANGE IS SMALLER
        {
            arr[k] = tmpArray2[j];
            j++;
        }
        else if (j == size2) // HANDLES THE CASE WHEN SECOND RANGE IS SMALLER
        {
            arr[k] = tmpArray1[i];
            i++;
        }
        else if ( tmpArray1[i] < tmpArray2[j] )
        {
            arr[k] = tmpArray1[i];
            i++;
        }
        else
        {
            arr[k] = tmpArray2[j];
            j++;
        }
    }

    delete[] tmpArray1; // IMPORTANT! DON'T FORGET TO RELEASE
    delete[] tmpArray2; // THE MEMORY YOU ALLOCATE...
}

void mergeSort( int arr[], int lb, int ub )
{
    if (ub - lb > 1)
    {
        int mid = (lb + ub) /  2;
        mergeSort(arr, lb, mid);
        mergeSort(arr, mid + 1, ub);
        myMerge(arr, lb, mid, ub);
    } 
    else // DON'T FORGET TO ADD YOUR BASE STEP! (sort a trivial range of 2 elements)
    {
        if (arr[ub] < arr[lb])
        {
            int tmp = arr[ub];
            arr[ub] = arr[lb];
            arr[lb] = tmp;
        }
    }
}

// SOME TESTING...

#include <iostream>
#include <iterator>
#include <algorithm>

using namespace std;

int main()
{
    int numbers[] = { 8, 40, 1, 5, 0, 9, 6, 4, 3, -1, 5 };
    mergeSort(numbers, 0, 10);
    copy(begin(numbers), end(numbers), ostream_iterator<int>(cout, " "));
}

【讨论】:

  • 谢谢,这解决了我访问数组超出其边界但不是重复元素问题的问题。
  • @James: 什么是错误排序的输入序列?
  • 我不确定你的意思是什么,如果我输入 26, 94, 82, 25, 64, 12, 74, 33, 60, 88 它会输出类似 -3368019, -33686019 , 33, 33, 33, 64, 64, 64, 64, 64.
  • @James:这很奇怪,我试过这个序列,它在这里工作。你只是复制粘贴我的代码,还是修复了你的?如果你修好了你的,有可能你犯了一些错误吗?此外,您提到的序列只有 10 个元素,因此范围从 0 到 9,您可以调用 mergeSort(numbers, 0, 9) 而不是我的示例中的 mergeSort(numbers, 0, 10)(其中序列有 11 个数字)。
  • 是的,我弄错了,谢谢。我有一个 1 在哪里,我应该在哪里。再次感谢您。
【解决方案2】:

我假设mergeSort 代码是正确的,这意味着ub 应该是要排序的范围的最后一个索引。如果不是这种情况,则mergeSort 的实现是错误的(merge 仍然是,但方式略有不同)。

填充tmpArray1时,您从范围之前访问一个元素:

for( i=0; i<size1; i++ )
{
    tmpArray1[i] = list[lb + i - 1];
}

范围中的第一个元素是list[lb],而不是list[lb-1]

在填充tmpArray2 时,您忽略了范围末尾的一个元素:

for( j=0; j<size2; j++ )
{
    tmpArray2[j] = list[mid + j];
}

那里应该是list[mid + 1 + j]

合并时,不会合并所有元素:

for( int k=lb; k<ub; k++ )
{
    if ( tmpArray1[i] <= tmpArray2[j] )
    {
        list[k] = tmpArray1[i];
            i++;
    }
    else
    {
        list[k] = tmpArray2[j];
        j++;
    }
}

循环控制中应该是k &lt;= ub

但是,最让我头疼的是

tmpArray1[size1 + 1] = INT_MAX;
tmpArray2[size2 + 1] = INT_MAX;

如果数组包含INT_MAX,或者如果元素类型为例如更大的值,那肯定会失败。 long long.

使用标记值标记数组的末尾是不合理的,您应该使用索引来检测它。

【讨论】:

  • 这些东西似乎都没有解决问题。它现在重复了两个数字。我的老师告诉我使用 INT_MAX。虽然我不认为他想用它来做模板。
【解决方案3】:

在这一行:

    tmpArray1[i] = list[lb + i - 1];

你的意思是这样的:

    tmpArray1[i] = list[lb + i];

否则,您会从给定的合并范围之外获取一个值,这将解释重复的数字。当您回写到列表时,您不会使用该逻辑。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多