递归,无法理解输出

时间:2017-06-18 16:06:58

标签: c# recursion

为什么我在输出中获得额外的1 * 1并且它有点倒退?有点初学者有递归,会喜欢详细的答案。

class Program
{

    public static long Factorial(int n)
    {
        if (n == 0)
            return 1;

        Console.WriteLine("{0} * {1}", n, Factorial(n - 1));
        return n * Factorial(n - 1);    
    }
    static void Main(string[] args)
    {
       long a = 0;
        a = Factorial(3);
        Console.WriteLine(a);
    }
}

输出

1 * 1
2 * 1
1 * 1
3 * 2
1 * 1
2 * 1
1 * 1
6

4 个答案:

答案 0 :(得分:4)

你在递归两次中调用函数,一次在输出中,然后再在下一行。这就是输出全部混乱的原因,因为您使用Console.WriteLine()方法调用它,然后立即在return语句中再次调用它。

此外,factorial为零是1,所以我在WriteLine()方法中添加了一个小的三元语句检查,以便输出在数学上是正确的。

Console.WriteLine("{0} * {1}", n, Factorial(n - 1));  // Calling it once
return n * Factorial(n - 1);    // Calling it again, oops!

这是一个有用的微调:

public static long Factorial(int n)
{
    if (n == 0)
        return 1;
    Console.WriteLine("{0} * {1}", n, (n>1?n-1:n));
    return n * Factorial(n - 1);    
}

static void Main(string[] args)
{
   long  a = Factorial(3);
    Console.WriteLine(a);
}

产量

3 * 2
2 * 1
1 * 1 
6

答案 1 :(得分:1)

这是因为日志输出中有第二个因子循环:

Console.WriteLine("{0} * {1}", n, Factorial(n - 1));

这需要在打印之前计算Factorial(n-1)。 因为递归的每一步现在都会触发两次递归,所以它比你想象的要复杂得多!

So Factorial(3):

  • 开始记录“3 * Factorial(2)” - >但需要解决因素(2)
    • Factorial(2)开始记录“2 * Factorial(1) - >但需要制定因子(1)
      • 因子(1)开始记录“1 * Factorial(0) - >但需要计算因子(0)
        • Factorial(0)返回1
      • 现在Factorial(1)可以打印“1 * 1”(您的第一行输出。)
      • 现在Factorial(1)需要计算Factorial(0)的返回值...
        • Factorial(0)返回1
    • Factorial(2)可以记录“2 * 1”(您的第二行输出。)
    • 因子(2)现在需要计算因子(1)才能返回
      • Factorial(1)开始记录“1 * Factorial(0) - >但需要制定因子(0)
      • ......等等。

如果您将其更改为:

Console.WriteLine("{0} * Factorial({1})", n, n - 1);

然后你会得到更像你期望的日志记录。

(如果您正在学习递归,那么您可能会感兴趣的是使用调试器并查看程序的流程如何,以及为什么它会导致您看到的输出。)

答案 2 :(得分:0)

碰巧,你的错误与递归无关。你的问题是,你调用一个方法两次,而不是保存它的值。如果你不打印结果,你甚至不会意识到这一点。正如@JLK指出的那样,你在这段代码中称它为两次:

Console.WriteLine("{0} * {1}", n, Factorial(n - 1));
return n * Factorial(n - 1);

让我们逐步完成代码,以便我们理解输出:

Factorial (3);
-> output 3 * {Factorial (2)}          // 3 * 2 (4)
   -> output 2 * {Factorial (1)}       // 2 * 1 (2)
      -> output 1 * {Factorial (0)}    // 1 * 1 (1)
         <- 1 
         Factorial (0)
      <- 2
      Factorial (1)
         -> output 1 * {Factorial (0)} // 1 * 1 (3)
         <- 1
         Factorial (0)
      <- 1
   <- 1
   Factorial (2)
   -> output 2 * {Factorial (1)}       // 2 * 1 (6)
      -> output 1 * {Factorial (0)}    // 1 * 1 (5)
         <- 1
         Factorial (0)
      <- 1
      Factorial (1)
         -> output 1 * {Factorial (0)} // 1 * 1 (7)
         <- 1
         Factorial (0)
      <- 1
   <- 2

这可能有点令人困惑,但这对于递归来说是正常的。所以你基本上所要做的就是稍微更改一下代码片段:

var factorial = Factorial (n - 1);
Console.WriteLine("{0} * {1}", n, factorial);
return n * factorial;

输出是:

1 * 1
2 * 1
3 * 2
6

如果您想了解有关递归的更多信息,我建议使用this网站。这不是关于C#,但它是一个学习递归的好网站。如果你想在递归中做很多事情,我不建议使用C#,因为它是一种必要的语言。对于递归,函数式语言更适合。如果您仍想接近C#,或者至少使用.Net框架,我建议使用F#,这是C#的功能等同。

C#和递归的主要问题是,C#不支持Tail recursion。当你进行更深层次的递归调用时(例如无限递归循环),这很容易导致StackOverflows。这是因为每次调用函数时,调用函数的代码地址都会被压入堆栈。因此,每次执行递归调用时,代码adddress都会被推送到堆栈。所以,如果你有这个功能:

void rec () => rec();

通过调用C#,您可以在不到一秒的时间内获得StackOverflow。然而,尾递归至少部分地解决了这个问题:如果递归调用是函数中的最后一次执行,它不会推送调用者的代码地址。所以在F#中,这将永远运行:

let rec r () =
    r ()
r ()

(我不会深入研究F#语法,有足够的在线教程)。我说它只是部分地解决了这个问题,因为这个:

let rec r () =
    r ()
    ()
r ()

再次产生StackOverflow。我不知道任何编程语言,但事实并非如此,所以你只需要习惯使用尾递归。

您可以在this帖子中阅读有关功能/递归语言的更多信息。

答案 3 :(得分:0)

你应该从记分牌上复制你的作业。你必须变成:

    Console.WriteLine("{0} * {1}", n, Factorial(n - 1));

通过

    Console.WriteLine("{0} * {1}", n, n - 1);

这个错误输出错误。