如何计算输出?

时间:2009-12-05 10:25:57

标签: c recursion

如何计算递归函数的输出?我知道递归调用一个堆栈,但在解决一些aptitude问题时很困惑。

给出以下代码片段:

#include <stdio.h>

void fun(int a){
 if(a>0){
    fun(--a);
    printf("%d ",a);
    fun(--a);
    printf("%d ",a);
 }
return;
}

int main(void){
  int  num = 5;
  fun(num);
return 0;
}

这不是任何家庭作业,但我无法在考试条件下解决这个问题。(无编译理论考试)

解决此类问题的标准方法是什么?请用一个小例子来解释。欢迎使用正确方向的指针或某个网站链接。

5 个答案:

答案 0 :(得分:8)

拿笔和纸;绘制函数的调用以及参数 - 你将有一种二叉树。跟踪执行并在页面上写入所有相关数据。它还可以帮助您了解功能。

当您在纸上绘制时,递归调用中涉及的分支(尤其是像这样的二进制调用)非常合乎逻辑且直观。这就是我在学校里被教导的方式 - 而且这是一种理解这样的东西的好方法,至少在一开始并不是一切都那么直观。

示例:

            fun [5]
          /        \
       fun[4]       fun[3]
      /      \        |    \
  fun[3]   fun[2]   fun[2]  fun[1]

我画了调用树,就像你在纸上画出来一样。这应该有助于让你发现更清楚的事情。这就是我当天处理这类东西的方式,所以请相信我 - 它有效:)

答案 1 :(得分:3)

编写递归方法有点像制定一个计划来排列多米诺骨牌并将其击倒。您必须预测每个递归调用(多米诺骨牌)将如何为完成整个任务添加其部分。

这意味着递归方法的“肉”不在递归部分,但在“结束情况”中,当您不打算重新调用该方法时执行的代码部分,最后一个多米诺骨牌在链中,你推动开始的乐趣。

那么,让我们看看你的第一个例子,一个(整数)除法的递归程序。你想要实现的除法算法是“对于正d和n,令n(0)为n。继续从n(i)逐步减去d,直到在某个步骤q中,n(q)小于d你的回答是q。“

关键是首先看一下END案例。如果在开始时n已经小于d?然后你做了“零步骤”,所以你的除法结果是0。

在伪代码中:

int divide(int n, int d) {
if (n < d) {
return 0;
}
....
}

现在如果n不小于d(大于或等于d)怎么办?然后我们想尝试使用较小的n在分割过程中的另一个步骤。也就是说,再次使用“相同的d”和n =“旧的n” - d运行除法函数。但是一旦THAT除法完成,它只告诉我们(n-d)/ d需要多少减法步骤。我们知道n / d还需要一步。因此,我们必须将该步骤添加到结果:

int divide(int n, int d) {
if (n < d) {
return 0;
} else {
return divide( n-d, d ) + 1;
}
}

第二次回归实际上说的是什么?它说:“我不知道如何自己计算结果,但我确实知道它比'divide(nd,d)'的结果更多。所以我将'推卸'给那个方法调用然后只需添加一个它给我回来的东西。“

这个过程还在继续。我们不断向链中添加“除”多米诺骨牌,直到我们达到分割操作,其中n“收缩”小于d ...我们的结束情况,我们的零结果。现在我们敲掉第一个多米诺骨牌(我们添加到链中的最后一个),返回“0”。多米诺骨牌开始下降。每当一个多米诺骨牌敲击另一个多米诺骨牌时,我们在方法结果中加上“1”,直到最后第一个方法调用是最后一个倒下的多米诺骨牌,并返回分割结果。

让我们尝试一些例子:

12/18:

分(12,18) ---&GT;返回0,因为12小于18

结果为0。

20/5:

divide(20, 5)
---> returns divide(20-5, 5) + 1
------> returns divide(15-5, 5) +1
---------> returns divide(10-5, 5) +1
------------> returns divide(5-5, 5) +1
---------------> returns 0, since 5-5 is 0, which is less than 5
and now the dominoes fall...
------------> returns 0 + 1
---------> returns 1 + 1
------> returns 2 + 1
---> returns 3 + 1
result is 4.

8/3:

divide(8, 3)
---> returns divide(8-3, 3) + 1
------> returns divide(5-3, 3) +1
---------> returns 0, since 5-3 is 2, which is less than 3
and now the dominoes fall...
------> returns 0 + 1
---> returns 1 + 1
result is 2.

答案 2 :(得分:1)

你必须是编译器和计算机。在你走的时候写下堆栈:

  

输入main,用5调用fun。   有趣的是,5大于0,所以   我先减少5到4,然后再打电话给

如果你正在写作,我会转到一边并“开始一个新的堆栈”

  

我输入4的乐趣,大于0   我减少了4到3,然后再次打电话给你了

重复

  

我输入3的乐趣,大于0   我减少3比2,然后再打电话给

再次重复

  

我输入2的乐趣,大于0   我减少2比1,然后再次打电话给

再一次

  

我输入1的乐趣,大于0   我将1减1,然后再打电话给

最后一次进入,这次

  

我输入0的乐趣,大于0   我回来了

现在你回到原来的位置:

  

我输入1的乐趣,大于0   我将1减1,然后再次打电话给我   我打印出0

在打印命令上,将其写入另一个空间,该空间现在只包含“0”。继续这个功能:

  

我输入1的乐趣,大于0   我将1减1,然后再次打电话给我   我打印出0   我将0减少到-1并再次打电话给

这是另一个堆栈,但-1不大于0,所以它什么都不做。我们回到功能:

  

我输入1的乐趣,大于0   我将1减1,然后再次打电话给我   我打印出0   我将0减少到-1并再次调用乐趣   我打印出-1

我们完成了这个堆栈。我们回到一个较旧的堆栈(我们刚刚完成了1的乐趣,所以寻找以“减1并再次调用乐趣”结束的堆栈):

  

我输入2的乐趣,大于0   我减少2比1,然后再次打电话给乐趣   我打印出1   我将1减1,然后再次打电话给

调用fun(0)什么都不做,所以我们返回并继续:

  

我输入2的乐趣,大于0   我减少2比1,然后再次打电话给乐趣   我打印1   我将1减1,然后再次打电话给我   我打印出0

然后我们进入下一个最旧的堆栈(我们刚刚完成了2的乐趣,所以寻找以“减少到2并再次调用乐趣”结束的堆栈):

  

我输入3的乐趣,大于0   我减少了3到2,然后再次打电话给我   我打印出2   我减少2比1,然后再打电话给

这是一个重要的节省时间!我们之前已经调用了fun(1),没有必要再次通过它。 fun(1)打印出来的是什么?查找,你会看到它为输出添加了“0-1”,所以节省时间,然后添加。

这一直持续到你完成为止。这是很多工作,但写下你当前的堆栈是完成它的最简单的方法。为了保持这个已经很久的答案简短,其余的由你决定。 :)

答案 3 :(得分:0)

在函数开头添加printf("%d\n", a);(在if语句之外)并运行代码以查看执行流程。您可以向函数添加另一个参数,该参数将递归深度作为参数,并使用它来缩进print语句。

以下程序的输出将帮助您了解执行流程。

#include <stdio.h>

void indent(int d)
{
        for(int i = 0; i < d; i++)
                printf("\t");
}
void fun(int a, int d)
{
    indent(d);
    printf("function called with a = %d\n", a);
    if(a>0)
    {
        fun(--a, d + 1);
        indent(d);
        printf("%d\n", a);
        fun(--a, d + 1);
        indent(d);
        printf("%d\n", a);
    }
    else
    {
        indent(d);
        printf("returning as a<%d> <= 0\n", a);
    }
    return;
}

int main(void)
{
    int  num = 5;
    fun(num, 0);
    return 0;
}

这是输出:

function called with a = 5
    function called with a = 4
        function called with a = 3
            function called with a = 2
                function called with a = 1
                    function called with a = 0
                    returning as a<0> <= 0
                0
                    function called with a = -1
                    returning as a<-1> <= 0
                -1
            1
                function called with a = 0
                returning as a<0> <= 0
            0
        2
            function called with a = 1
                function called with a = 0
                returning as a<0> <= 0
            0
                function called with a = -1
                returning as a<-1> <= 0
            -1
        1
    3
        function called with a = 2
            function called with a = 1
                function called with a = 0
                returning as a<0> <= 0
            0
                function called with a = -1
                returning as a<-1> <= 0
            -1
        1
            function called with a = 0
            returning as a<0> <= 0
        0
    2
4
    function called with a = 3
        function called with a = 2
            function called with a = 1
                function called with a = 0
                returning as a<0> <= 0
            0
                function called with a = -1
                returning as a<-1> <= 0
            -1
        1
            function called with a = 0
            returning as a<0> <= 0
        0
    2
        function called with a = 1
            function called with a = 0
            returning as a<0> <= 0
        0
            function called with a = -1
            returning as a<-1> <= 0
        -1
    1
3

跟踪递归真的很有趣。享受!

答案 4 :(得分:-1)

这是一个难以回答的问题,只有笔和纸,我认为这是为了确保你已经理解了计算机是如何做到的,特别是对fun的两次递归调用。

在纸上模拟执行时,请记住,在递归调用fun时,处理器会记住局部变量的内容(此处参数为a哪个语句为在被叫函数结束后返回。