【问题标题】:return not exiting recursive method as expected - C#返回未按预期退出递归方法 - C#
【发布时间】:2018-09-10 13:59:08
【问题描述】:

这应该是相当基本的。 我觉得我错过了一些明显的东西,但我已经盯着这个看了一段时间了。 我有一个创建 Collat​​z 序列的递归方法(用作递归练习) 但是,一旦我的方法达到退出要求,我就会有一个“返回值”,然后跳回到方法内的递归调用。 如果有人能告诉我我错过了什么,我将不胜感激! 谢谢,下面的代码!

public int sequenceCreator(int currentVal, int numOfSteps)
    {
        int nextVal;
        while (currentVal != 1)
        { 
            if (currentVal % 2 == 0)
            {
                nextVal = currentVal / 2;
                numOfSteps++;
            }
            else // (currentVal % 2 > 0)
            {
                nextVal = (currentVal * 3) + 1;
                numOfSteps++;
            }
            return sequenceCreator(nextVal, numOfSteps);
        }

        return numOfSteps;
    }

【问题讨论】:

  • 你做递归错误。摆脱 while 循环。
  • 为什么while 语句从不执行多次?
  • “一旦我的方法达到退出要求,我就会有一个“返回值”,然后它会跳回到方法内的递归调用。” - 这就是递归的工作原理。您建立一个“堆栈”,一旦满足您的退出标准,它将返回给调用者,调用者将返回给它的调用者......直到到达初始调用者。
  • 一旦sequenceCreator 的实例在内部实例返回时再次调用自身,它将返回到递归发生点的原始调用,即调用递归方法的位置。这听起来就像您所看到的,并且正是我们期望发生的。你能澄清一下你看到的问题到底是什么吗?即您是否收到错误、不正确的结果等?
  • @ThomasFox 我不认为混乱是你的错。你并没有真正理解递归,这对理解递归至关重要;D

标签: c# recursion collatz


【解决方案1】:

return 仅退出该特定方法调用。它不会在调用sequenceCreator() 的所有时间中止。

在这种情况下,应该没问题,因为它返回到这一行:

return sequenceCreator(nextVal, numOfSteps);

这又会再次返回给它的调用者,依此类推,直到您最终解决所有递归调用。

但我可能会写这样的方法,而不是:

public int sequenceCreator(int currentVal)
{
    if (currentVal == 1) return 1;

    if (currentVal % 2 == 0)
    {
        return 1 + sequenceCreator(currentVal / 2);
    }
    else // (currentVal % 2 > 0)
    {
        return 1 + sequenceCreator(currentVal * 3 + 1);
    }
}

该代码为相同的输入产生相同的结果,但代码更少,更易于理解,并且不需要在方法调用之间传递额外的状态。

为了好玩,我们可以使用三元运算符进一步减少代码(但我不建议使用此版本,因为 imo 可读性受到影响):

public int sequenceCreator(int currentVal)
{
    if (currentVal == 1) return 1;
    return 1 + sequenceCreatetor(currentValue % 2 == 0? currentVal / 2 : currentVal * 3 + 1);
}

我展示这个版本是为了说明为什么使用递归。递归问题本质上是堆栈问题。对于每个递归解决方案,都存在一个仅依赖于传统方法的匹配解决方案,而是使用堆栈(递归解决方案只是“隐藏”堆栈,依赖于程序的调用堆栈)。但是,对于某些类型的问题,递归可以大幅减少解决它所需的代码量。所以我们也有相反的情况;如果您发现自己使用堆栈,可能有一种方法可以使用递归来大大简化问题。在这种情况下,方法体只有两行,如果我真的想要,我可以将其简化为一行代码。

您还需要了解这不会创建序列。在完全收敛的情况下,它只创建一个值。如果你真的想创建一个序列,你需要返回一个IEnumerable,最好使用yield关键字:

public IEnumerable<int> sequenceCreator(int currentVal)
{
    if (currentVal == 1) yield return 1;

    if (currentVal % 2 == 0)
    {
        yield return 1 + sequenceCreator(currentVal / 2);
    }
    else // (currentVal % 2 > 0)
    {
        yield return 1 + sequenceCreator(currentVal * 3 + 1);
    }
}

从逻辑上讲,我认为你在这里很安全,它会收敛。此方法的“最终”或基本情况是1 的输入。输入甚至减少到该基本情况的调用。输入为奇数的调用远离基本情况,但是下一个输入总是偶数,并且总是与我们之前尝试过的不同的偶数值。最终,我们希望得到 2 的幂,或者 3 乘以 2 的幂,即使它一直减少到 32,然后是 1,然后退出。

但是,我担心可能存在一些永远不会达到此状态的值,或者在它们可能之前溢出整数,或者在增加几次后减少到我们已经得到的偶数值尝试过,从而创造了一个永无止境的循环。

【讨论】:

  • 是的,我明白这一点。我只是试图返回具有特定起始值的序列中的值的数量。
【解决方案2】:

我认为不应该有任何循环。使用 if 条件代替 while。

如果 (currentVal != 1)

【讨论】:

  • 好的,谢谢。有人提到我不应该使用 while 循环
  • @ThomasFox while 很好。它不是必需的,也可能令人困惑,但它不会破坏任何东西。
【解决方案3】:

尽可能避免递归:

        int sequenceNumber = Convert.ToInt32(Console.ReadLine());
        List<int> list = new List<int>();

        while (sequenceNumber>=1)
        {
            if (sequenceNumber == 1)
            {
                Console.WriteLine(1);
                sequenceNumber = Convert.ToInt32(Console.ReadLine());
            }

            else if(sequenceNumber>1)
            {
                while (sequenceNumber>=1)
                {
                    if (sequenceNumber == 1)
                    {
                        list.Add(sequenceNumber);
                    }

                    else if (sequenceNumber % 2 == 0)
                    {
                        list.Add(sequenceNumber);
                        sequenceNumber = sequenceNumber / 2;

                    }
                    else if (sequenceNumber % 2 != 0)
                    {
                        list.Add(sequenceNumber);
                        sequenceNumber = sequenceNumber * 3 + 1;

                    }
                }

                list.ForEach(Console.WriteLine);
                foreach (int i in list)
                {
                    Console.Write(i + " ");

                }
            }

            sequenceNumber = Convert.ToInt32(Console.ReadLine());
        } 
    }

【讨论】:

  • 该回答什么问题以及如何回答?
  • 一个包含很多更改的代码帖子,似乎没有充分的理由去做,也没有解释,也没有明确的迹象表明这可以解决任何问题。
  • 我没有投反对票,但我回答的中间部分解释了为什么您有时确实想使用递归。
猜你喜欢
  • 2018-12-05
  • 2015-01-17
  • 2020-02-08
  • 2013-02-24
  • 1970-01-01
  • 2017-11-08
  • 2013-10-15
  • 2017-10-21
  • 1970-01-01
相关资源
最近更新 更多