时隔一年,又重新学数据结构二叉树部分,被各种递归搞得苦不堪言,以下是网上资料的一些汇总整理
函数的递归调用与分治策略
递归方法是算法和程序设计中的一种重要技术。递归方法即通过函数或过程调用自身将问题转化为本质相同但规模较小的子问题。递归方法具有易于描述和理解、证明简单等优点,在动态规划、贪心算法、回溯法等诸多算法中都有着极为广泛的应用,是许多复杂算法的基础。递归方法中所使用的“分而治之”的策略也称分治策略。
递归方法的构造
构造递归方法的关键在于建立递归关系。这里的递归关系可以是递归描述的,也可以是递推描述的。下面由一个求n的阶乘的程序为例,总结出构造递归方法的一般步骤。
[例1]从键盘输入正整数N(0<=N<=20),输出N!。
[分析]N!的计算是一个典型的递归问题。使用递归方法来描述程序,十分简单且易于理解。
[步骤1]描述递归关系 递归关系是这样的一种关系。设{U1,U2,U3,…,Un…}是一个序列,如果从某一项k开始,Un和它之前的若干项之间存在一种只与n有关的关系,这便称为递归关系。
注意到,当N>=1时,N!=N*(N-1)!(N=1时,0!=1),这就是一种递归关系。对于特定的K!,它只与K与(K-1)!有关。
[步骤2]确定递归边界 在步骤1的递归关系中,对大于k的Un的求解将最终归结为对Uk的求解。这里的Uk称为递归边界(或递归出口)。在本例中,递归边界为k=0,即0!=1。对于任意给定的N!,程序将最终求解到0!。
确定递归边界十分重要,如果没有确定递归边界,将导致程序无限递归而引起死循环。例如以下程序:
#include <iostream.h>
int f(int x){
return(f(x-1));
}
main(){
cout<<f(10);
}
它没有规定递归边界,运行时将无限循环,会导致错误。
[步骤3]写出递归函数并译为代码 将步骤1和步骤2中的递归关系与边界统一起来用数学语言来表示,即
N*(N-1)! 当N>=1时
n!=
1 当N=0时
再将这种关系翻译为代码,即一个函数:
long f(int n){
if (n==0)
return(1);
else
return(n*f(n-1));
}
[步骤4]完善程序 主要的递归函数已经完成,将程序依题意补充完整即可。
//ex1.cpp
#include <iostream.h>
long f(int n){
if (n==0)
return(1);
else
return(n*f(n-1));
}
main(){
int n;
cin>>n;
cout<<endl<<f(n);
}
综上,得出构造一个递归方法基本步骤,即描述递归关系、确定递归边界、写出递归函数并译为代码,最后将程序完善。以下继续引用一些例子来讨论递归方法的应用。
经典递归问题
以下讨论两个十分经典的递归问题。它们的算法构造同样遵循刚刚得出的四个步骤。了解这两个问题可加深对递归方法的理解。
[例2]Fibonacci数列(兔子繁殖)问题:已知无穷数列A,满足:A(1)=A(2)=1,A(N)=A(N-1)+A(N-2)(N>=3)。从键盘输入N,输出A(N)。
[分析]递归关系十分明显,由A(N)的表达式给出。需要注意的是本例中对于N>=3,A(N)的值与A(N-1)和A(N-2)都有关。
[代码]
//ex2.cpp
#include <iostream.h>
long fibonacci(int x)
{
if ( (x==1) || (x==2) )
return(1);
else
return(fibonacci(x-1)+fibonacci(x-2));
}
main(){
int n;
cin>>n;
cout>>endl>>fibonacci(n);
}
[例3]Hanoi塔问题。
[问题描述]在霍比特人的圣庙里,有一块黄铜板,上面插着3根宝石针(分别为A号,B号和C号)。在A号针上从下到上套着从大到小的n个圆形金片。现要将A针上的金片全部移到C针上,且仍按照原来的顺序叠置。移动的规则如下:这些金片只能在3根针间移动,一次只能一片,且任何时候都不允许将较大的金片压在较小的上面。从键盘输入n,要求给出移动的次数和方案。
[分析]由金片的个数建立递归关系。当n=1时,只要将唯一的金片从A移到C即可。当n>1时,只要把较小的(n-1)片按移动规则从A移到B,再将剩下的最大的从A移到C(即中间“借助”B把金片从A移到C),再将B上的(n-1)个金片按照规则从B移到C(中间“借助”A)。
本题的特点在于不容易用数学语言写出具体的递归函数,但递归关系明显,仍可用递归方法求解。
[代码]
//ex3.cpp
#include <iostream.h>
hanoi(int n,char t1,char t2,char t3){
if (n==1)
cout<<"1 "<<t1<<" "<<t3<<endl;
else
{
hanoi(n-1,t1,t3,t2);
cout<<n<<" "<<t1<<" "<<t3<<endl;
hanoi(n-1,t2,t1,t3);
}
}
main(){
int n;
cout<<"Please enter the number of Hanoi:";
cin>>n;
cout<<"Answer:"<<endl;
hanoi(n,'A','B','C');
}
函数递归调用的应用与分治策略
许多算法都采用了分治策略求解,而可以说分治与递归是一对孪生兄弟,它们经常同时被应用于算法的设计中。下面讨论著名的Catalan数问题,人们在对它的研究中充分应用了分治策略。
[例4]Catalan数问题。
[问题描述]一个凸多边形,通过不相交于n边形内部的对角线,剖分为若干个三角形。求不同的剖分方案总数H(n)。H(n)即为Catalan数。例如,n=5时H(5)=5。
[分析]Catalan数问题有着明显的递归子问题特征。在计算Catalan数时虽然可以推导出只关于n的一般公式,但在推导过程中却要用到递归公式。下面讨论三种不同的解法,其中第三种解法没有使用递归,它是由前两种解法推导而出的。
[解法1]对于多边形V1V2…Vn,对角线V1Vi(i=3,4,…,n-1)将其分为两部分,一部分是i边形,另一部分是n-i+1边形。因此,以对角线V1Vi为一个剖分方案的剖分方案数为H(i)*H(n-i+1)。还有一种的特殊情形,是对角线V2Vn将其分为一个三角形V1V2Vn和一个n-2+1边形。为了让它同样符合粗体字给出的公式,规定H(2)=1。于是得到公式:
H(n)=∑H(i)*H(n-i+1) (i=2,3,…,n-1) ----公式(1)
H(2)=1
有了这个递归关系式,就可以用递推法或递归法解出H(n)。
[解法2]从V1向除了V2和Vn外的n-3个顶点可作n-3条对角线。每一条对角线V1Vi把多边形剖分成两部分,剖分方案数为H(i)*H(n-i+2),由于Vi可以是V3V4…Vn-1中的任一点,且V1可换成V2,V3,…,Vn中任一点也有同样的结果。考虑到同一条对角线在2个顶点被重复计算了一次,于是对每个由顶点和对角线确定的剖分方案都乘以1/2,故有
H(n)=n∑(1/2)H(i)*H(n-i+2) (i=3,4,…,n-1)
把(1/2)提到∑外面,
H(n)=n/(2*(n-3))∑H(i)*H(n-i+2) (i=3,4,…,n-1) ----公式(2)
规定H(2)=H(3)=1,这是合理的。
由公式(2)和H(2)=1,同样可以用递推法或递归法解出H(n)。
[解法3] 把公式(1)中的自变量改为n+1,再将刚刚得出的公式(2)代入公式(1),得到
H(n+1)=∑H(i)*H(n-i+2) (i=2,3,…,n) 由公式(1)
H(n+1)=2*H(n)+∑H(i)*H(n-i+2) (i=3,4,…,n-1) 由H(2)=1
H(n+1)=(4n-6)/n*H(n) 由公式(2)
H(n)=(4n-10)/(n-1)*H(n-1) ----公式(3)
这是一个较之前两种解法更为简单的递归公式,还可以继续简化为
H(n)=1/(n-1)*C(n-2,2n-4) ----公式(4)
这就不需要再使用递归算法了。然而在程序设计上,公式(4)反而显得更加复杂,因为要计算阶乘。因此最后给出由公式(3)作为理论依据范例程序代码。代码相当简单,这都归功于刚才的推导。如果用前两种解法中的递归关系,程序会变得复杂且容易写错。因此,有时对具体问题将递归关系公式进行必要的化简也是至关重要的。
[代码]
//ex4.cpp
#include <iostream.h>
#define MAXN 100
long f(int x){
if (x==3)
return(1);
else
return((4*x-10)*f(x-1)/(x-1));
}
main(){
int n;
cout<<"\nPlease input N for a Catalan number:";
cin>>n;
if ( (n<=MAXN) && (n>=3) )
cout<<"The answer is:"<<f(n);
}
本例编程时还有一个细节问题需要注意。注意函数f中的斜体部分,按照公式(4)计算时一定要先进行乘法再进行除法运算,因为(4*x-10)并不总能整除(x-1),如果先进行除法则除出的小数部分将自动被舍去,从而导致得到不正确的解。
数学上许多有重要意义的计数问题都可以归结为对Catalan数的研究。可以看到,本例中的递归关系经简化还是相当简单的。下面讨论一个递归关系略为复杂的例子。
[例5]快速排序问题。
快速排序是程序设计中经常涉及的一种排序算法。它的最好时间复杂度为O(nlog2n),最差为O(n2),是一种不稳定的排序方法(大小相同的数在排序后可能交换位置)。
[算法描述]快速排序的一种基本思想是:要将n个数按由小到大排列,在待排序的n个数中选取任一个数(在本例中取第一个),称为基准数,在每一次快速排序过程中设置两个指示器i和j,对基准数左边和右边的数同时从最左(i)和最右(j)开始进行扫描(i逐1递增,j逐1递减),直到找到从左边开始的某个i大于或等于基准数,从右边开始的某个j小于或等于基准数。一旦发现这样的i和j(暂且称之为一个“逆序对”),则把第i个数和第j个数交换位置,这样它们就不再是逆序对了,紧接着再将i递增1,j递减1。如此反复,在交换过有限个逆序对后,i和j将越来越靠近,最后“相遇”,即i和j指向同一个数,暂且称之为相遇数(极端情况下,如果一开始就不存在逆序对,i和j将直接“相遇”)。相遇后就保证数列中没有逆序对了(除了在上述的极端情况下基准数和自身也算构成一个逆序对,注意粗体字给出的逆序对的定义)。继续扫描,非极端情况下,由于数列中已经没有逆序对,i递增1(如果相遇数小于基准数)或者j递减1(如果相遇数大于基准数)后即算完成了一趟快速排序,这时第1到第j个数中的每个都保证小于或等于基准数,第i到第n个数中的每个保证大于或等于基准数。此时,递归调用函数,对第1到第j个数和第i到第n个数分别再进行一趟快速排序。如果在极端情况下,程序认为基准数和自身构成逆序对,则将基准数与自身交换(这其实没有作用)之后i递增1,j递减1(注意斜体字给出的对逆序对的处理方法),同样对第1到第j个数和第i到第n个数分别再进行一趟快速排序。
最后的问题就是确定递归边界。由于被排序的数列将不断被划分为两个至少含一个数的子列(因为在每趟排序最后进行递归调用函数时i<>j),最后子列的长度将变为1。这就是递归的边界。在程序实现是,本着“能排则排”的原则,只要第一个数小于j(或者第i个数小于最后一个数),即进行递归。
[主程序(递归函数体)]
void QuickSort(RecType R[ ],int s,int t)
{
int i=s,j=t,k;
RecType temp;
if (s<t)
{
temp=R[s] // 用区间第1个记录作为基准
while( i!=j) //从两端向中间交替扫描,直至i=j;
{
while( j>i&&R[j].key>temp.key)
j--;
if(i<j)
{
R[i]=R[j];
i++;
}
while( i<j&&R[i].key<temp.key)
i++;
if(i<j)
{
R[j]=R[i];
j--;
}
}
R[i]=temp;
QuickSort(R,s,i-1);
QuickSort(R,i+1,t);
}
}
[例6]“九宫阵”智力游戏。
[问题描述]一个9×9方阵,由9个“九宫格”组成,每个九宫格又由3×3共9个小格子组成。请在每个空白小格子里面填上1~9的数字,使每个数字在每个九宫格内以及在整个九宫阵中的每行、每列上均出现一次。
(1)编程将下面图中的九宫阵补充完整。
(2)讨论是否可能给出“九宫阵”的全部解?
| 9 |
|
|
| 4 | 5 |
| 7 |
|
| 3 |
| 7 | 9 | 4 |
|
|
|
|
| 3 | 6 |
|
| 8 | 9 |
3 |
|
| 1 |
|
|
|
|
|
|
| 4 |
|
|
|
| 2 | 3 |
| 1 | 2 |
|
| 3 |
|
|
|
8 |
|
|
|
|
|
| 5 |
|
| 6 |
| 2 | 9 |
|
|
|
|
| 2 | 1 |
|
|
| 8 |
|
|
[分析]本题可利用回溯法解决,其基本思想为深度优先搜索(DFS),这也是一种以分治策略为基础的算法。回溯法与纯粹的DFS不同的是,它在搜索过程中不断杀死不合题意的结点。这一点保证了解法的效率。
首先考虑如何得出全部解的情况。
解空间树容易构造,只需按顺序(从第一行第一个数字开始到第一行最后一个,然后第二行……,一直到最后一行最后一个数字)“尝试”填入数字即可。
为了解决这个问题,我们需要先编写一个函数check,其原型为int check(int i,int j,int k),用于求第i行第j列能否填上数字k。如果可以,返回1,否则返回0。由于我们是按顺序填入数字的,看起来一个数字后面的数字并不在判断能否填的范围内。但为了解决题中某个特解问题的方便,还是引入较为严谨的判断方法。
函数check代码如下:
int check(int i,int j,int k){
int l,m,pi,pj;
//1. Check the line
for (l=1;l<=9;l++)
if ( (l!=j) && (a[i][l]!=0) && (a[i][l]==k) )
return(0);
//2. Check the column
for (l=1;l<=9;l++)
if ( (l!=i) && (a[l][j]!=0) && (a[l][j]==k) )
return(0);
//3. Check the 3x3 matrix
//3.1 Firstly we will have to check the parent_i(pi) and parent_j(pj)
if (i<=3) pi=1;
else if (i<=6) pi=4;
else pi=7;
if (j<=3) pj=1;
else if (j<=6) pj=4;
else pj=7;
//3.2 Now we can check it
for (l=0;l<=2;l++)
for (m=0;m<=2;m++){
if ( ((pi+l)!=i) && ((pj+m)!=j) )
if ( ( a[pi+l][pj+m]!=0 ) && ( a[pi+l][pj+m]==k ) )
return(0);
}
return(1);
}
结合注释很容易就能接受函数的思想,不予过多说明。
下面考虑程序最重要的部分,即递归函数。思路是这样的:假设某一格能填入某数,把这个格子看成解空间树的一个结点,由它可以扩展出9个儿子,即下一格填什么数(由1到9逐个尝试)。对下一格,同样这样考虑。不断用函数check函数考察某一个能否填入某数,一旦函数check返回0,则杀死这个结点。
如果能一直填到最后一个数,结点仍未被杀死,则这是一个解。
这种思想可用伪代码表示如下:
procedure backtrack(i,j,k:integer);
if check(i,j,k)=true then
begin
a[i,j]=k;
Generate_next_i_and_j;
if i<10 then
begin
for l:=1 to 9 do
backtrack(i,j,l);
end
else
Do_Output;
a[i,j]:=0;
end;
注意斜体的“a[i,j]:=0”必不可少!当对某个结点(x,y)扩展的过程中,可能在扩展到(x+m,y+n)时它的子树被完全杀死(每个结点都被杀死,亦即按照(x,y)及之前的填数方案填数,无解)。这时需要保证(x,y)以后所填的数被重新置零,这个语句的作用即在每个结点被杀死时都将其置零。
将伪代码翻译为C++代码:
backtrack(int i,int j,int k){
int l;
if (check(i,j,k)==1){
a[i][j]=k; //Fill in the okay solution
//Generate next i,j
if (j<9) j++;
else { i++; j=1; } //End of Generate next i,j
if (i<10){
for (l=1;l<=9;l++)
backtrack(i,j,l);
}
else
output();
a[i][j]=0; /*When fails and goes upperwards, the value must be cleared*/
}
}
函数output()用双重循环完成输出。在主函数main()对backtrack(1,1,i)进行一个循环,i从1取到9,即可完成整个程序。运行时发现九宫格的解相当多,即使保存到文件中也不现实。这就回答了第2个问题。
对于第1个问题,将这个程序略加改动,即赋予全局数组a以初值,并在过程backtrack中产生下一个i和j时跳过有初值的部分,即可将程序转化为求填有部分空缺的九宫格程序。
最后给出填充有部分空缺的九宫格的完整源代码。
[代码]
#include <iostream.h>
int a[11][11]={0};
int check(int i,int j,int k){
int l,m,pi,pj;
//1. Check the line
for (l=1;l<=9;l++)
if ( (l!=j) && (a[i][l]!=0) && (a[i][l]==k) )
return(0);
//2. Check the column
for (l=1;l<=9;l++)
if ( (l!=i) && (a[l][j]!=0) && (a[l][j]==k) )
return(0);
//3. Check the 3x3 matrix
//3.1 Firstly we will have to check the parent_i(pi) and parent_j(pj)
if (i<=3) pi=1;
else if (i<=6) pi=4;
else pi=7;
if (j<=3) pj=1;
else if (j<=6) pj=4;
else pj=7;
//3.2 Now we can check it
for (l=0;l<=2;l++)
for (m=0;m<=2;m++){
if ( ((pi+l)!=i) && ((pj+m)!=j) )
if ( ( a[pi+l][pj+m]!=0 ) && ( a[pi+l][pj+m]==k ) )
return(0);
}
return(1);
}
output(){
int i,j;
cout<<"One solution is:"<<endl;
for (i=1;i<=9;i++)
{
for (j=1;j<=9;j++)
cout<<a[i][j]<<" ";
cout<<endl;
}
}
backtrack(int i,int j,int k){
int l;
if (check(i,j,k)==1)
{
a[i][j]=k; //Fill in the okay solution
//Generate next i,j
do{
if (j<9) j++;
else { i++; j=1; }
} while (a[i][j]!=0); //End of Generate next i,j
if (i<10)
{
for (l=1;l<=9;l++)
backtrack(i,j,l);
}
else
output();
a[i][j]=0; /*When fails and goes upperwards, the value must be cleared*/
}
}
init(){
a[1][2]=9; a[1][6]=4; a[1][7]=5; a[1][9]=7;
a[2][3]=3; a[2][5]=7; a[2][6]=9; a[2][7]=4;
a[3][4]=3; a[3][5]=6; a[3][8]=8; a[3][9]=9;
a[4][1]=3; a[4][4]=1;
a[5][3]=4; a[5][8]=2; a[5][9]=3;
a[6][2]=1; a[6][3]=2; a[6][6]=3;
a[7][1]=8; a[7][8]=5;
a[8][2]=6; a[8][4]=2; a[8][5]=9;
a[9][2]=2; a[9][3]=1; a[9][7]=8;
}
main(){
int i;
for (i=1;i<=9;i++){
init();
backtrack(1,1,i);
}
}
递归方法在算法与数据结构中的应用无所不在,如动态规划(状态方程)、回溯法(深度优先搜索)等等,以上两例只是冰山一角。只有熟悉掌握函数递归调用的编程方法,深入理解分治策略的重要思想,才能编写出功能强大、高效简明的程序。
所谓递归,简而言问。在函数中直接调用函数本身,称为直接递归调用。在函数中调用其它函数,其它函数又调用原函数,这就构成了函数自身的间接调用,称为间接递归调用。,具有很好的可读性,还往往使某些看起来不易解决的问题变得容易解决。但在递归函数中,由于
递归,作为C语言最经典的算法之之就是在调用一个函数的过程中又直接或间接地调用该函数本身,以实现层次数据结构的查询和访一,是一种非常有用的程序设计方法。虽然用递归算法编写的程序结构清晰存在着自调用过程,程序控制反复进入其自身,使程序的分析设计有一定困难,致使很多初学者往往对递归迷惑不解,也在这上面花了不少的时间,却收效甚微。那么,程使问题得到解决。
说明:使用其他的办法比较麻究竟什么是递归?怎么实现递归呢?
而采用递归方法来解决问难解决,而使用递归的方法可以很好地解决问题
1、可以把要解决用递归调的问题转化为一个新问题,而这个新的问题的解决方法仍与原来的解决方法相同,只是所处理的对象有规律地递增或递减。
说明:解决问题的方法相同,调用函数的参数每次不同(有规律的递增或递减),如果没有规律也就不能适用。
3、必定要有一个明确的结束递归的题,必须符合以下三个条件:
2、可以应用这个转化过烦或很条件。
说明:一定要能够在适当的地方结束递归调用。不然可能导致系统崩溃。
好知道是这样以后;我,求n!的问题可以转化为n*(n-1)!的新问题。比如n=4:
第一部分: n-2)!:3
第三部分:2*1 (n-2)(n-3)!
第四部分:1 (n-4)! 4*3*2们来写一个众多教材上的程序:使用递归的方法求n!。
当n>1时*1 n*(n-1)!
第二部分*2*1 (n-1)( 4-4=0,得到值1,结束递归。
我给h>
int fac(int n)
{int c;
printf("now t的源程序如下:
#include <stdio.he number is %d ",n);
getchar();
if(n==1 || n==0) c=1; -1);
printf("now the number is %d
else c=n*fac(n and the %d! is %d",n,n,c);
getchar();
return c;}
void main()
{ int n=4; 执行过程。运行结果如图1所示,当主函数第一次调用fac()函数的时候,由于n=4不等于0和1,并不立即返回结果1,而是执行c=n*fac(n-1),用实参n-1(值为3)调用fac()函数自己,即递归调用fac(3)。于是进入第二层调用fac(),这时也没有得出结果,继续用实参n-1(值为
printf("result is %d.\n",fac(n)); }
可以看到,加上两条printf()和getchar()语句后,可以察看各级调用及其中间答案,很清楚的看到程序的2)调用fac()函数自己。同样经过第三层调用后进入第四层调用,这时候n=1,算出1!=1,返回值算出4!=4*3!=4*6=24,结束整个递归调用,得出最终结果并输出。满足结束递归的条件,然后把得出的结果1返回给第三次调用的fac函数,得出2*1!=2,然后把结果2返回给第二次调用的fac函数,得出3*2!=6,最后第一次调用的fac函数根据第二次调用的
我们做事情,一般都是从头开始的,而递归却是从末尾开始的。比如上面的函数,当n>1的时候,就只能求助于n-1,而(n-1)>1时,就求助于n-2,然后……直到(n-k)=1时,函数fac终于有了返回值1了,它再从头开始计算,然后一直算到n为止。所以说,递归简直就是一个数学模型,它的工作过程就是自己调用自己。以下是几点对递归的说明:
1、当函数自己调用自占用的存储单元也就越己时,系统将自动把函数中当前的变量和形参暂时保留起来,在新一轮的调用过程中,系统为新调用的函数所用到的变量和形参开辟另外的存储单元(内存空间)。每次调用函数所使用的变量在不同的内存空间。
2、递归调用的层次越多,同名变量的多。一定要记住,每次函数的调用,系统都会为该函数的变量开辟新的内存空间。上一层的调用点,同时取得当初进入该层时,函数中的变量和形参所占用的内存空间的数据。
4、在开发过程中使用printf()和getchar()可以看到执行过程,并且可以在发现错误后停止运行。
很多人说所有递归问题都可以用非递较好的可读性,因此很多问题用递归可很容易解决。同时由于递归调用过程中,系统要为每一层调用中的变量开辟内存空间、要记住每一层调用后的返回点、要增加许多额外的开销,因此函数的递归调用通常会降低程序的运行效率(在许多情况下,速度的差别不太明显)。
过这样一个动物繁殖问题:若一头小母牛,从出生起第四个年头开始每年生一头母牛,按此规律,第n年时有多少头母牛?
如果不用递归函数来做,每当母牛到第4岁的时候才会生下一头小母牛,所以,每年增加的新的1岁小母牛都是上一年3岁的 3、当本用非递归的方法往往使程序变得十分复杂难以读懂,而函数的递归调用在解决这类问题时能使程序简洁明了有
我曾经碰到母牛加上4岁的母牛生下数量之和次调用的函数运行结束时,系统将释放本次调用时所占用的内存空间。程序的流程返回到归的方法来解决,能不用递归就不用递归。但是对于一些比较复杂的递归问题,分析过程如图2所示
给出程序如下:
main()
{int i,n; scanf("%d",&n);
for(i=2;i<=n;i++)
{ f4=f4+f3;f3=f2;f2=f1;f1=f4;}
printf(“now you have %
int f1=1,f2=0,f3=0,f4=f(n)=f(n-1)+f(n-3)呢,请看:
f(n)-f(n-1)=f(n-3)
因为第n年要比n-1年多的牛,都是0;
printf("pleas但是可读性太差,不易理解。那么如果用递归函数求此问题呢?
我们先写出函数表达式:f(n)=f(n-1)+f(n-3)
为什么大于三岁的牛生的小牛,而e input how many years it past:\n");
d cattle!\n “,f1+f2+f3+f4); }
程序虽然简短, f(n-3)正是那些在n年大于三岁的牛,然后它们在第n年生下相同数量的小牛。
源代码如下:
#include< e(ct,n-1)+cattle(ct,n-3)); }
main()stdio.h>
int cattle(int ct,int n)
{ if(n<4) return (ct);
else return (cattl
{int ct=1,n; se input how many years it past:\n");
scanf("%d",&n);
printf("you have %d cattle
printf("plea now!\n",cattle(ct,n));}
运行结果如图3所示:见,递归函数的主要优点是可以把算法写的比使用非递归函数时更清晰更简洁,而且某些问题,特别是与
可人工智能有关的问题,更适宜用递归方法。递归的另一个优点是,递归函数不会受到怀方使用if语句,强迫函数在未执行递归调用前返回。如果不这样做,在调用函数后,它永远不会返回,,较非递归函数而言,某些人更相信递归函数。编写递归函数时,必须在函数的某些地能解决,但是造成无穷递归。在递归函数中不使用if语句,是一个很常见的错误。此外,象汉诺塔问题就只能靠递归才疑现实中很多问题都是比较简单的,没有象汉诺塔那么复杂,我们只要会用递归编程来为我们解决一些问题就行了,所以就不必深究了。