【问题标题】:c# find max value recursive (fastest)c#递归查找最大值(最快)
【发布时间】:2021-12-08 12:25:32
【问题描述】:

我对此一无所知。最初我自己尝试过,然后从 SO 和 google 复制,它适用于除一个以外的所有情况,但是在我的作业中仍然没有找到对特定测试用例足够快的递归算法:/

无论如何,这是为什么:

        public static int FindMaximum(int[] array)
        {
            if (array is null)
            {
                throw new ArgumentNullException(nameof(array));
            }

            if (array.Length == 0)
            {
                throw new ArgumentException(null);
            }

            return FindMaxRec(array, array.Length);
        }

        public static int FindMaxRec(int[] arr, int n)
        {
            if (n == 1)
            {
                return arr[0];
            }

            return Math.Max(arr[n - 1], FindMaxRec(arr, n - 1));
        }

不适用于此TestCase?:

        [Test]
        [Order(0)]
        [Timeout(5_000)]
        public void FindMaximum_TestForLargeArray()
        {
            int expected = this.max;
            int actual = FindMaximum(this.array);
            Assert.AreEqual(expected, actual);
        }

编辑 1:

虽然这很好用,但我需要递归:

        public static int FindMaximum(int[] array)
        {
            if (array is null)
            {
                throw new ArgumentNullException(nameof(array));
            }

            if (array.Length == 0)
            {
                throw new ArgumentException(null);
            }

            int maxValue = int.MinValue;

            for (int i = 0; i < array.Length; i++)
            {
                if (array[i] > maxValue)
                {
                    maxValue = array[i];
                }
            }

            return maxValue;
        }

【问题讨论】:

  • 什么是“this.max”和“this.array”?这是理解测试用例的关键。
  • 有什么限制?不允许排序吗?另外,为什么不尝试迭代方法而不是递归呢?
  • @Linascts 迭代会更好,只需一次扫描就足够了。应该以 O(N) 时间复杂度运行。递归会增加栈帧的开销。
  • 嗯,您有一个长度为 10.000.000 的数组,因此您的递归深度为 10.000.000。难怪这很慢(如果它甚至到达终点并且没有异常)。在这种用例中,recusion 的整个想法是“分而治之”。我个人建议一种算法,它将数组分成两半,然后在每一半上递归调用它,然后从这两个结果中取最大值。这样做直到两半的长度都为 1 这将使您的递归深度从 10000000 下降到 24
  • @derpirscher 将数组分成两半似乎是正确的解决方案(Dmitry 的解决方案),我在 Nigel 的解决方案上实现枚举器时遇到了一些困难,但它也应该可以工作。

标签: c# arrays testing recursion max


【解决方案1】:

您可以尝试在两个中拆分数组:

public static int FindMaximum(int[] array) {
  if (null == array)
    throw new ArgumentNullException(nameof(array));
  if (array.Length <= 0)
    throw new ArgumentException("Empty array is not allowed.", nameof(array));

  return FindMaxRec(array, 0, array.Length - 1);
}

private static int FindMaxRec(int[] array, int from, int to) {
  if (to < from)
    throw new ArgumentOutOfRangeException(nameof(to));
  if (to <= from + 1)
    return Math.Max(array[from], array[to]);

  return Math.Max(FindMaxRec(array, from, (from + to) / 2),
                  FindMaxRec(array, (from + to) / 2 + 1, to));
}

演示:

Random random = new Random(123);

int[] data = Enumerable
  .Range(0, 10_000_000)
  .Select(_ => random.Next(1_000_000_000))
  .ToArray();

Stopwatch sw = new Stopwatch();

sw.Start();

int max = FindMaximum(data);

sw.Stop(); 

Console.WriteLine($"max  = {max}");
Console.WriteLine($"time = {sw.ElapsedMilliseconds}");

结果:

max  = 999999635
time = 100

【讨论】:

  • 很好,我明白你关于递归深度的观点。
【解决方案2】:

将简单的线性算法转换为递归算法的一种简单方法是利用数组的enumerator

    public static int FindMax(int[] values)
    {
        using var enumerator = values.GetEnumerator();
        return FindMaxRecursively(enumerator, int.MinValue);
    } 

    private static T FindMaxRecursively<T>(IEnumerator<T> enumerator, T currentMax) where T : IComparable
    {
        if (!enumerator.MoveNext()) return currentMax;
        var currentValue = enumerator.Current;
        if (currentValue.CompareTo(currentMax) > 0) currentMax = currentValue;
        return FindMaxRecursively(enumerator, currentMax);
    }

这会通过您的测试用例并使用递归。

编辑:这是上面的一个对初学者更友好的版本,用 cmets 来解释它在做什么:

    public static int FindMax(IEnumerable<int> values)
    {
        using var enumerator = values.GetEnumerator();//the using statement disposes the enumerator when we are done
        //disposing the enumerator is important because we want to reset the index back to zero for the next time someone enumerates the array
        return FindMaxRecursively(enumerator, int.MinValue);
    } 


    private static int FindMaxRecursively(IEnumerator<int> enumerator, int currentMax)
    {
        if (!enumerator.MoveNext()) //move to the next item in the array. If there are no more items in the array MoveNext() returns false
            return currentMax; //if there are no more items in the array return the current maximum value
        var currentValue = enumerator.Current;//this is the value in the array at the current index
        if (currentValue > currentMax) currentMax = currentValue;//if it's larger than the current maximum update the maximum
        return FindMaxRecursively(enumerator, currentMax);//continue on to the next value, making sure to pass the current maximum
    }

可能有助于理解这一点的是IEnumerator 是启用foreach 循环的原因。在后台,foreach 循环只是在具有IEnumerator 的项目上重复调用MoveNextHere 是关于该主题的更多信息。

【讨论】:

  • 如果我们只使用递归,我喜欢这种方法。
  • 我可以看到这会过去,但是我还不能理解。我想知道我在写什么。有没有一种简单的方法可以做到这一点?像初学者级别的语法或类似的东西?
  • @Linascts 我已经更新了我的答案,包括一些更适合初学者的代码,以及解释每个步骤的 cmets
  • 我不太确定,这种方法不会遇到与 OP 方法相同的问题。是的,它会(一段时间后)找到正确的最大值。但它会为大输入数组建立一个巨大的递归堆栈(因此可能会引发堆栈溢出异常)
【解决方案3】:
public static int findMax(int[] a, int index) {
    if (index > 0) {
        return Math.max(a[index], findMax(a, index-1))
    } else {
        return a[0];
    }
}

【讨论】:

  • 这在测试用例上不起作用,我之前已经使用过google的这个例子。
猜你喜欢
  • 1970-01-01
  • 2021-03-23
  • 2021-11-11
  • 1970-01-01
  • 2015-05-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多