函数的递归(常见三种问题)

圆切割问题

给你一个圆,请用n刀去切割它,求能得到的最多片数,如图:

如果用q(n)来代表切n刀能得到的最多的片数,那么我们能发现以下结论:

q(0) = 1
q(1) = 1 + 1 = 2
q(2) = 1 + 1+ 2 = 4
q(3) = 1 + 1 + 2 + 3 = 7
q(4) = 1 + 1 + 2 + 3 + 4 = 11
......
q(n) = q(n-1) + n

很容易想到用循环,也就是迭代方法解决问题,比如:

int q(int n)
{
    int c = 1;
    for(int s = 1;s <= n;s++)
        c += s;
    return c;
}

如果我们将上面那个结论反过来看,即:

q(n) = q(n - 1) + n
q(n - 1) = q(n - 2) + n - 1
......
q(1) = q(0) + 1
q(0) = 1

就会发现每一个q(n)都依赖于它的前一个值q(n - 1),有一种递归关系在其中,这样我们就可以用递归来实现这个算法了,比如:

int q(int n)
{
    if(n == 0)
        return 1;
    return q(n - 1) + n; //与公式中的形式一样
}

递归方式与递推方式相比,区别在于关注点不同。

  • 递推方式关注点在起始值

  • 递归方式关注点在求解目标

虽然它们都在表现第n次和第n-1次之间的关系,但递归方式更加明显,而且更加简洁

总的来说,递归算法的关键在于以下几点:

  • 找到第n次与第n-1次之间的关系。

  • 确定第1次的结果。

汉诺塔问题

这是一个经典的递归问题:

有一根杆子上从下往上串着n个依次增大的盘子,请利用第二根杆子,将这些盘子移动到第三根杆子上。

要求一次只能移动一个盘子,且大盘子不能放到小盘子上。

对于这个问题,我们可以采用递归的方式来思考:

  • 先将上面n-1个碟子移动到第二个柱子上。

  • 然后将第n个碟子移动到第三个柱子上。

  • 最后将第二个柱子上的n-1个碟子移动到第三个柱子上。

如果我们用一个函数move(n,from,tmp,to)来表示移动盘子的动作,其中:

  • n为要移动的碟子数。

  • from为移动的碟子所在的柱子。

  • tmp为移动过程中能借助的柱子。

  • to为碟子要移动到的目的柱子。

则上面的过程可以表示为:

move(n - 1,from,to,tmp);
move(1,from,tmp,to);
move(n - 1,tmp,from,to);

如果只有一个盘子要移动,那就直接将其移动到目的柱子上,即:

if(n == 1)
    printf("%c->%c\n",from,to);

将这两段代码组合起来,就是:

void move(int n,char from,char tmp,char to)
{
    if(n == 1)
        printf("%c->%c\n",from,to);
    else
    {
        move(n - 1,from,to,tmp);
        move(1,from,tmp,to);
        move(n - 1,tmp,from,to);
    }
}

如果用3ABC来调用它,即move(3,'A','B','C'),得到的结果是:

A->C
A->B
C->B
A->C
B->A
B->C
A->C

可以根据输出的步骤模拟移动一下,看看是否正确!

总的来说,使用递归模拟连续发生的动作,有以下步骤:

  • 确定连续发生的动作

  • 确定连续次动作之间的关系

  • 确定边界条件

放苹果问题

把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?

注意:5,1,1和1,5,1 是同一种分法。

我们可以先假设有一个函数count(m,n)能告诉我们m个苹果放n个盘子有多少种放法,然后在此基础上进行分析:

根据mn之间的关系,可以分成以下两种情况来讨论:

①. 如果m小于n,即苹果(m)数比盘子(n)数小,那么无论怎么放,总是会至少有n - m个空盘子。因此可以得到:count(m,n) = count(m,m)

②. 如果m大于等于n,即苹果数比盘子数多,或者相同,则有两种放置方法:

  • 所有盘子都要放苹果。这个时候苹果中的**n个的位置已经固定**,只要考虑剩下m-n个,因此可以得到:count(m,n) = count(m - n,n)

  • 有盘子不放苹果。在这种情况中,我们可以只考虑有一个盘子不放。因为在不断的往下的过程中,盘子数可以不断的减一,隐含了在m个苹果放在n - 1 ,n - 2 ... 2 ,1个盘子中的情况。这个时候可以得到count(m,n) = count(m,n - 1)

综合以上两点,可以得到代码:

int count(int m,int n)
{
    if(m < n)
        return count(m,m);
    else
        return count(m - n,n) + count(m,n - 1); //两种放置方法的和
}

现在还差一个终止条件。考虑盘子数或者苹果数各为1或者0时,有:

  • 盘子数为0或者1,苹果无论有多少都只有一种分配方法。

  • 苹果数为0或者1,无论如何都只能放在一个盘子里。

即得到:

if(m <=1 || n <= 1)
    return 1;

上下两段代码组合起来,就得到:

int count(int m,int n)
{
    if(m <=1 || n <= 1)
        return 1;
    if(m < n)
        return count(m,m);
    else
        return count(m - n,n) + count(m,n - 1); //两种放置方法的和
}

当我们输入2 2时,即count(2,2),得到的结果是:

2

即两种放法:每个盘子放一个苹果,或者两个苹果放在一个盘子里。

总的来说,利用递归进行自动分析的方法如下:

  • 假设有一个函数能给出答案。

  • 再利用这个函数,分析如何解决问题

  • 搞清楚最简单的情况下答案是什么。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值