数据结构编程实验(第3版)
上QQ阅读APP看书,第一时间看更新

3.1 计算递归函数

数学上常用的阶乘函数、幂函数、斐波那契数列,其定义和计算都是递归的。例如,对于自然数n,阶乘n!的递归定义为。按阶乘n!的递归定义,求解n!的递归函数fac(n)如下。


int  fac(int n);
{
    if (n==0) return 1;            //判断递归边界
    if (n>=1) return n*fac(n-1);   //处理递归并返回结果
}

显然,递归程序的最大优点是程序简明、结构紧凑。这种直接从问题定义出发编程的方法最便于人们阅读和理解。但问题是,递归过程中的状态变化比较难掌握,效率也比较低。以fac(3)为例,它的执行流程如图3.1-1所示。

图 3.1-1

程序中fac(0)=1称为递归边界。fac(3)→fac(2)→fac(1)→fac(0)称为递归过程,接下来的fac(0)→fac(1)→fac(2)→fac(3)是一个回代过程(fac(0)=1回代给fac(1),fac(1)的值回代给fac(2)……直至求出fac(3)=6)。

类似地,对于自然数n,计算斐波那契数列的函数fib(n)递归定义为,根据递归定义的形式,可写出如下递归函数:


int fib(int n);
{ if (n<=1) return  n;                    //递归边界
    if (n>1) return  fib(n-1)+fib(n-2);   //递归步骤
}

从上述两个例子可以得到如下启示。

1)用递归过程求解递归定义的函数,递归过程可直接按照递归函数的定义结构编写。

2)对于一个较复杂的问题,若能够分解成几个相对简单且解法相同或类似的子问题,只要解决了这些子问题,则原问题就迎刃而解,这就是递归求解。例如,计算阶乘4!时先计算3!,将3!的结果值回代就可以求出4!(4!=4*3!)了。这种分解–求解的策略叫“分治法”。

3)当分解后的子问题可以直接解决时,就停止分解,可直接求解的子问题称为递归边界。递归的过程就是不断地向递归边界靠近,若递归函数无法到达边界,则程序会因栈溢出而失败退出。例如,阶乘的递归边界是fac(0)=1,斐波那契数列的递归边界是fib(0)=0,fib(1)=1。

【3.1.1 放苹果】

把m个同样的苹果放在n个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?用k表示不同的分法。5、1、1和1、5、1是同一种分法。

输入

第一行是测试用例的数目t(0≤t≤20)。以下每行均包含两个整数m和n,以空格分开,1≤m,n≤10。

输出

对输入的每个测试用例m和n,用一行输出相应的k。

试题来源:lwx@POJ

在线测试:POJ 1664

试题解析

设f(m,n)是m个同样的苹果放在n个同样的盘子里的方法数,对f(m,n)的分析和递归定义如下。

1)n>m:至少有n-m个盘子会空着,去掉这n-m个盘子对放苹果的方法数不会产生影响,即f(m,n)=f(m,m)。

2)n≤m:不同的放法可以分成两类。

·至少有一个盘子没有放苹果,即f(m,n)=f(m,n-1)。

·所有盘子里都有苹果,如果从每个盘子里拿走一个苹果,不会影响方法数,即f(m,n)=f(m-n,n)。

所以,当n≤m时,根据加法原理,放苹果的方法数等于上述两者的和,即f(m,n)=f(m,n-1)+f(m-n,n)。

对f(m,n)的递归边界分析如下:

1)当n==1时,所有苹果都必须放在一个盘子里,所以f(m,n)返回1;

2)当m==0时,没有苹果可放,定义为1种放法,f(m,n)返回1。

递归过程,或者是n减少,向递归边界逼近,最终到达递归边界n==1;或者是m减少,当n>m时,f(m,n)返回f(m,m),最终到达递归边界m==0。

参考程序


#include<cstdio>
using namespace std;
int f(int m,int n)             //递归函数:计算m个同样的苹果放在n个同样的盘子里的方法数
{
    if(n==1||m==0)             //处理递归边界
        return 1;
    if(m<n)                    //处理情况n>m
        return f(m,m);
    else                       //处理情况n≤m
        return f(m,n-1)+f(m-n,n);
}
int main()
{
    int t;
    scanf("%d",&t);            //输入测试用例数
    while(t--)                 //依次处理每个测试用例
    {
        int m,n;
        scanf("%d%d",&m,&n);   //输入苹果数和盘子数
        printf("%d\n",f(m,n)); //计算和输出m个苹果放在n个盘子里的方法数
    }
    return 0;
}