【问题标题】:Merge Sort returning all zeroes归并排序返回全零
【发布时间】:2017-11-06 04:03:22
【问题描述】:

最终合并的输出是[0,0,0,0]。我认为它适用于数组左半部分的第一次合并,因为它正确排列。我想帮忙调试一下。

static public void Main(string[] args)
{
    int[] input = { 4, 1, 3, 2};
    MergeSort(input, 0, input.Length - 1);
    for (int i = 0; i < input.Length; i++)
    {
        Console.WriteLine(input[i]);
    }
    Console.ReadLine();
}

static public void MergeSort(int[] input, int left, int right)
{
    if (left < right)
    { 
        int middle = (left + right) / 2;
        MergeSort(input, left, middle);
        MergeSort(input, middle + 1, right);
        Merge(input, left, (middle + 1), right);
    }
}

static public void Merge(int[] input, int left, int middle, int right)
{
    int tempindex = left;
    int[] tmp = new int[input.Length];
    int rightpointer = middle + 1;
    int leftpointer = left; 
    while (leftpointer <= middle && rightpointer <= right)
    {
        if (input[leftpointer] < input[rightpointer])
        {
            tmp[tempindex] = input[leftpointer];
            leftpointer++; 
        }
        else
        {
            tmp[tempindex] = input[rightpointer];
            rightpointer++;
        }
        tempindex++; 
    }
    while (leftpointer <= middle)
    {
        tmp[tempindex] = input[leftpointer];
        leftpointer++; 
        tempindex++;
    }
    while (rightpointer <= right)
    {
        tmp[tempindex] = input[rightpointer];
        right++;
        tempindex++;
    }

    for (int i = 0; i < tmp.Length; i++)
    {
        input[i] = tmp[i];
    }
}

【问题讨论】:

  • 在调试器中单步执行代码时看到了什么?
  • 提示:for (int i = 0; i &lt; tmp.Length; i++) - 这里有问题。
  • 有许多在线资源可以帮助您,其中大多数都提供了足够体面的解释来说明它是如何完成的。一些示例:MergeSort.csMerge Sorting Algorithm in C#C# - Sorting Algorithm - Merge Sort Recursive
  • 老实说,我的大脑太疲惫了,我似乎找不到它。尝试将其更改为 for (int i = left; i

标签: c# mergesort


【解决方案1】:

稍微更改了代码以使其正常工作。请参考一些在线资源以更好地理解算法

private static void Merge(int[] input, int left, int middle, int right)
    {
        int[] temp = new int[input.Length];
        int rightpointer = middle - 1;
        int tempindex = left;
        int num = right - left + 1;

        while ((left <= rightpointer) && (middle <= right))
        {
            if (input[left] <= input[middle])
            {
                temp[tempindex] = input[left];
                tempindex++;
                left++;
            }
            else
            {
                temp[tempindex] = input[middle];
                tempindex++;
                middle++;
            }
        }

        while (left <= rightpointer)
        {
            temp[tempindex] = input[left];
            tempindex++;
            left++;
        }

        while (middle <= right)
        {
            temp[tempindex] = input[middle];
            tempindex++;
            middle++;
        }

        for (int i = 0; i < num; i++)
        {
            input[right] = temp[right];
            right--;
        }
    }

【讨论】:

    【解决方案2】:

    我将尝试从更高层次的角度解释你的错误,而不是简单地发布你的代码的固定版本,我认为这是一个毫无意义和无益的练习,不会对你有任何重要的好处,而是关于如何避免将来出现类似错误的一些建议。

    您的代码有几个问题。它们分为三类:

    1. 一个接一个。经典的编程错误,在处理索引时,尤其是在处理那些边缘情况的边界值时,您应该始终保持警惕。
    2. 简单的错字。另一个经典的编程错误。在这种情况下,很可能发生这种情况是您复制并粘贴了 while 循环,然后输入了错误的变量名。您应该尽量避免复制/粘贴;在经典的归并排序的情况下,这实际上是不可避免的,因此您应该遵循推论规则:如果您在复制/粘贴时必须更改粘贴代码中的变量名,请仔细检查您的工作!李>
    3. 概念。这是“经典”,因为程序员很难学习良好的高级概念化和抽象技能,但它以许多不同的方式表现出来(与前两种不同),因此更难提供一般性建议。我最好的建议是几年前我的一位教授喜欢说的话:KISS,或“keep it simple, stupid”。或者我喜欢表达同一件事的方式:“懒惰是好的”。无论哪种方式,重点是尽可能地简化问题。如果您必须手工完成,请让您的数据建模接近您想要解决问题的方式,并确保如果您必须手工完成,您将以最简单、劳动强度最低的方式完成.

    那么,这些错误出现在哪里?从最后到第一个……

    从概念上讲,您的代码中最大的问题是您的 Merge() 方法分配的数组是 input 数组的完整大小,即使您知道确定该方法是假定的只考虑该数组的一个子集。 (你知道,因为这是该方法的其他三个参数的重点。)

    这在后面的方法中造成了一个问题,因为当你将tmp数组复制回input时,你只是盲目地写了一个循环来复制所有的数据。即使该方法实际上仅将值写入tmp 数组的子集也是如此。最终,这就是为什么您的输出全为零的原因。并非tmp 数组中的所有值都已写入,因此它们仍然具有0 的默认值,然后您将这些值复制到input 数组中,覆盖您真正想要的原始值。

    如果您为tmp 分配了一个数组,该数组的大小仅足以包含您在该特定调用期间实际要操作的数据,那么在最后如何将数据复制回来将会非常清楚。在最坏的情况下,你会得到一个IndexOutOfRangeException,这是一个更多比在最后得到错误输出更好的发现错误的方法(这是@987654322 的一个例子@principal,另一个我喜欢的概念)。

    现在,关于那个错字。坦率地说,一旦你有触发它的数据,你最终会发现这个错误。碰巧的是,您的输入数据集没有,因为它永远不会进入错误所在的循环。但是,Merge() 方法中的最后一个 while 循环是递增 right 变量而不是 rightindex 变量。一旦tempindex 变量变得太大,这将导致IndexOutOfRangeException(因此,至少它会快速失败:))。

    FWIW,除了简单地确保在复制/粘贴时对工作进行双重和三重检查之外,恕我直言,这是一个示例,说明为什么在这样的简单循环中,实际上希望使用更简洁的 put 语法数组中的后增量表达式。这样做可确保您递增的索引肯定是您用于索引数组的索引:

    while (rightpointer <= right)
    {
        tmp[tempindex++] = input[rightpointer++];
    }
    

    最后,至于你的非一错误,这可以避免,不仅要在处理索引的边界值时非常小心,而且要始终牢记一些心理表征(我喜欢可视化数据结构) 值之间的关系。

    在这种特殊情况下,您为Merge() 方法的middle 参数传递的值处理不一致。也就是说,在MergeSort()方法中,middle值显然被用作“左侧”侧分区的上界。这可以在对MergeSort() 方法的递归调用中看到。但是,当您调用 Merge() 时,您会将该调用的 middle 参数的值加一。

    然而,在Merge() 方法中,您仍然使用参数来指示左分区的上限。当您将middle + 1 作为该值传递时,您是在告诉Merge() 方法将数组的不同部分视为左分区,而不是在调用Merge() 之前在对MergeSort() 的递归调用中实际排序的部分.这对算法的结果具有明显且有害的后果。 :)

    如果您更加努力地可视化这些分区以及定义它们的变量与分区之间的关系,那么您一开始就不太可能编写错误的代码。

    这是您的代码的更正版本,注释行显示了我所做的更改:

    static public void Main(string[] args)
    {
        int[] input = { 4, 1, 3, 2 };
        MergeSort(input, 0, input.Length - 1);
        for (int i = 0; i < input.Length; i++)
        {
            Console.WriteLine(input[i]);
        }
        Console.ReadLine();
    }
    
    static public void MergeSort(int[] input, int left, int right)
    {
        if (left < right)
        {
            int middle = (left + right) / 2;
            MergeSort(input, left, middle);
            MergeSort(input, middle + 1, right);
    
            // was: Merge(input, left, (middle + 1), right);
            Merge(input, left, middle, right);
        }
    }
    
    static public void Merge(int[] input, int left, int middle, int right)
    {
        // was: int tempindex = left;
        int tempindex = 0;
    
        // was: int[] tmp = new int[input.Length];
        int[] tmp = new int[right - left + 1];
    
        int rightpointer = middle + 1;
        int leftpointer = left;
        while (leftpointer <= middle && rightpointer <= right)
        {
            if (input[leftpointer] < input[rightpointer])
            {
                tmp[tempindex] = input[leftpointer];
                leftpointer++;
            }
            else
            {
                tmp[tempindex] = input[rightpointer];
                rightpointer++;
            }
            tempindex++;
        }
        while (leftpointer <= middle)
        {
            tmp[tempindex] = input[leftpointer];
            leftpointer++;
            tempindex++;
        }
        while (rightpointer <= right)
        {
            tmp[tempindex] = input[rightpointer];
    
            // was: right++;
            rightpointer++;
    
            tempindex++;
        }
    
        for (int i = 0; i < tmp.Length; i++)
        {
            // was: input[i] = tmp[i];
            input[left + i] = tmp[i];
        }
    }
    

    最后,我没有写任何关于调试的东西。但实际上,尝试修复代码的第一步是使用调试器逐步完成它。当您编写代码时,您已经想到了您期望代码采取的各种操作和结果。当您调试代码时,您要寻找的是任何违反这些预期的地方。

    如果您单步执行代码,您会发现违反预期的第一件事就是将未初始化的 0 值从 tmp 数组复制到 input 数组。您可能会也可能不会以与我相同的方式解决该特定问题 - 实际上,当时最方便的方法是简单地更改 for 循环的边界,仅复制您实际写入值的那些元素(我以我的方式修复它,因为它使用更少的内存,并且因为它是编写代码的示例,以便在出现错误时可以更快地失败)——但至少你会发现问题。

    【讨论】:

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