当前位置: 首页 > 知识库问答 >
问题:

C-如何跟踪这种递归?

庄高谊
2023-03-14

我一直在网上看递归(C语言)的例子,试图更好地理解它和它是如何工作的。一般来说,我可以毫无问题地跟踪一些基本的递归问题(比如阶乘问题),但是我发现了这个问题,并且完全不知道如何跟踪它。

这个想法是让用户输入一个变化量,通过使用递归,您打印出可以进行变化量的方式数量。代码如下:

#include <stdio.h>

#define NUM_DENOMS 4

int ways(int amount, int denomination);

int main()
{
    //Declarations & initializations
    int userChange = 0;

    //Get user input
    printf("Enter an amount you wish to get change for (in cents):\n");// get the amount of change in from the user
    scanf("%d", &userChange);

    //Function call... pass user's input and denomination values (ints) as parameters
    printf("%d cents can be made in %d different ways\n", userChange, ways(userChange, NUM_DENOMS));

    return 0;
}

//Function to find # of ways to make change for user's amount
int ways(int amount, int denomination)
{
    static int validAmounts[NUM_DENOMS] = {1, 5, 10, 25};

    if(denomination<=0) //if denomination is invalid
    {
        return 0;
    }
    if((amount == 1) || (amount == 0)) //base case: only 1 combination
    {
        return 1;
    }
    else if(amount < 0) //can't have negative amount of money
    {
        return 0;
    }
    else //if denomination is valid and user's change > 1
    {
        return ways(amount, denomination-1) + ways(amount-validAmounts[denomination-1], denomination);
    }
}

显然,这是递归的常见应用。不过,我无法理解这种递归是如何工作的。对我来说最突出的是,同一条线路上有2个递归调用。我从未见过以这种方式应用递归。

我确实试图追踪它,但我的结果肯定是错误的:

假设我输入 25 作为更改量。当我进入函数方式时,没有一个基本情况得到满足,因此递归开始发挥作用。对于第一次调用,金额保持不变,面额减少 1,因此我们回到函数中,将 25 和 3 (4-1) 作为我们的新参数。在面额减少到0之前,不会满足任何基本情况(因为金额永远不会改变)。此时,我们将返回 0。这就是我迷路的地方。我看到0通过所有以前的调用被发送回去,所以最终结果是0,但这对我来说听起来不对。在尝试跟踪第二个调用时,我遇到了同样的问题,除了不是通过调用发送回0,而是1。显然,我对这种递归的看法是非常错误的。有人可以向我解释这个递归实例是如何工作的吗?

共有2个答案

壤驷凯
2023-03-14

代码进行了两次调用,因为它将问题分成两个部分,每个部分都以相同的方式解决。每个部分在某种意义上比原始问题更简单,并且使用相同的方法来解决每个单独的问题。正如其他人所指出的,可能存在两个以上部分的情况。

您可能已经看到过一个调用的示例,其中问题的一部分已解决,而单个递归调用解决了问题的“剩余部分”。

唐海阳
2023-03-14

跟踪递归算法的一种方法是在递归函数的顶部放置一个printfprintf应该打印出函数的参数。临时添加更多参数以获得有关递归正在做什么的额外信息也是一个好主意。最常见的附加参数是深度参数,它显示已进行了多少嵌套调用。对于这个特定问题(您有两个递归调用),我将添加一个附加参数来确定正在跟踪哪个调用。

考虑到这一点,这里是修改后的代码。我建议从一个简单的输入开始,比如5,来了解递归是如何工作的。

#include <stdio.h>

#define NUM_DENOMS 4

int ways(int amount, int denomination, int left, int depth);

int main( void )
{
    int userChange = 0;

    printf("Enter an amount you wish to get change for (in cents):\n");
    scanf("%d", &userChange);

    printf("%d cents can be made in %d different ways\n", userChange, ways(userChange, NUM_DENOMS, 'M', 0));

    return 0;
}

int ways(int amount, int denomination, int left, int depth)
{
    static int validAmounts[NUM_DENOMS] = {1, 5, 10, 25};

    printf( "%2d %d %c %2d\n", amount, denomination, left, depth );

    if(denomination <= 0 || amount < 0)
        return 0;

    if((amount == 1) || (amount == 0))
        return 1;

    return ways(amount, denomination-1, 'L', depth+1) + ways(amount-validAmounts[denomination-1], denomination, 'R', depth+1);
}
 类似资料:
  • Project Euler问题14给出以下问题: 为正整数集定义以下迭代序列: n→n/2(n为偶数) n→3n 1(n为奇数) 使用上述规则,从13开始,我们生成以下序列: 13→ 40→ 20→ 10→ 5.→ 16→ 8.→ 4.→ 2.→ 1. 可以看出,该序列(从13开始,到1结束)包含10个术语。虽然这还没有被证明(科拉兹问题),但人们认为所有的起始数字都以1结束。 100万以下的哪个

  • 第一步,登录niushop商城后台,在设置菜单栏下找到配送管理-->物流配送->物流跟踪设置,提供快递100免费版和企业版两个接口,在后台看见,免费版只需要APPKEY一个参数,企业版需要APPKEY和CUSTOMER两个参数。 如何申请这两个接口?获取这些参数? 第二步,登录快递100官网,注册账号,选择快递接口,进行接口申请。 根据个人需要选择,免费或者企业的接口,填写基本信息,提交。 注意:

  • 1、登录niushop商城后台,在设置菜单栏下找到配送管理-->物流配送->物流跟踪设置,选择快递鸟接口,需要我们填写应用APPID和应用密钥。 2、登录快递鸟 官网,如果有快递鸟账号可以直接登录,没有则需要进行申请注册。 3、登录快递鸟后台,就可以看见需要的APPID和APPKEY,这时还需要我们进行实名认证。 点击上方 用户信息处的“未认证”进行资质认证。 4、认证通过之后,在我的产品服务中选

  • 问题内容: 在程序快要结束时,我希望将类的所有实例中的特定变量加载到字典中。 例如: 假设实例数量会有所不同,我希望将Foo()的每个实例的x dict加载到新的dict中。我该怎么办? 我在SO中看到的示例假定一个已经具有实例列表。 问题答案: 跟踪实例的一种方法是使用类变量: 在程序结束时,您可以像下面这样创建字典: 只有一个列表:

  • 我做了很多年的Java开发人员,主要是使用spring开发MVC Web应用程序。我正在学习Kotlin和Android作为一个自我开发项目,并且大部分都很喜欢它。我通常只是把事情弄清楚,但我认为我在这里遗漏了一些重要的东西(因为我喜欢编写易于维护且不容易出现异常的代码)。我理解与Java的互操作性,我只是对我的Kotlin代码是如何编译的感到困惑,并且对Java方法调用抛出异常没有任何警告。 下

  • 跟踪行为控制着 Entity Framework Core 是否会在其变更跟踪器里维持实体实例的信息。如果实体是被跟踪的,任何检测到的该实体的变更都将在 SaveChanges() 时持久化到数据库中。Entity Framework Core 还会对已跟踪的、之前已加载到 DbContext 实例中的查询和实体进行相互的导航属性装配。 提示 你可以在 GitHub 上查阅当前文章涉及的代码样例。