递归:一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法
递归是我们经常是能够用到的解题思想,前面我已经简单的说了一下递推,今天我们来念叨念叨递归。我认为许多的题目都可以同时用递归和递推的思想实现,但是往往递推的效果更好,后面会举例子说明的。
先来一个最简单的例子吧——求解N!
long f(int n)
{
long g;
if(n==1) //递归出口
g=1;
else
g=n*f(n-1); //递归调用
return g; //递归函数返回值
}
例如求解5!,递归调用与返回的实施过程如下:
f(5)—-f(4)—-f(3)—-f(2)—-f(1)—-f(2)—-f(3)—-f(4)—-f(5)
—————————> 递归段 ————————>回溯段
应用递归求解问题的基本步骤:构建递归关系,确定边界关系,写出递归函数,最后设计主函数带实参调用递归函数。
例二:汉诺塔游戏(随便画的),是求解将n个圆盘从A桩移动到C桩的移动次数。
当n=1,A上面只有一个圆盘,只需要移动1次即可完成;
当n=2,首先将小盘移动到B桩上面,然后将大盘移动到C桩上面,最后将小盘移动到C盘上,用了3次;
设移动n个盘需要g(n)次完成,分以下三个步骤完成:
1.首先将n个盘上面的n-1个盘借助C桩从A移动到B桩,需要g(n-1)次;
2.然后将最大的盘移动到C盘上面,用了1次;
3.最后,将B桩上的n-1个圆盘,借助A桩移动到C桩,需要g(n-1)次;
因而有递归的关系:
g(n)=2*g(n-1)+1
初始条件(递归出口):
g(1)=1
函数描述:
double g(int n)
{
doble s;
if(n==1)
s=1;
else
s=2*g(n-1)+1;
return s;
}
下面设计程序,展示n个圆盘的移动过程
设置move(n,a,b,c)表示将n个圆盘从A桩借助B桩移动到C桩的过程。函数show(a,c)表示输出从A桩移动到C桩的一次移动过程,即A->C
实现move(na,b,c),当n=1时,即show(a,c);
当n>1时,分以下三步走:
1.将A桩上的n-1个盘借助于C桩移动到B桩,即move(n-1,a,c,b);
2.将A桩上的第n个盘移动到C桩,即show(a,c);
3.将B桩上的n-1个盘借助于A桩移动到C桩,即move(n-1,b,a,c);
同时设置K来统计移动的次数:
#include <stdio.h>
long k=0;
void show(char x,char y)
{
printf("%c-->%c",x,y)
k++;
if(k%5==0)
printf("\n");
}
void move(n,a,b,c)
{
if (n==1)
show(a,c);
else
{
move(n-1,a,c,b);
show(a,c);
move(n-1,b,a,c);
}
}
void main(){
int n;
printf("\n input n:");
scanf("%d",&n);
move(n,a,b,c);
printf("\n 移动的次数为:%1d\n",k);
}
例三:排队购票问题。一场球赛开始前,售票工作正在紧张的的进行。每张球票售价为50元,现有30个人排队等待,其中有20人手持50元的钞票,有10人手持100元的钞票。假设开始售票时,售票处没有零钱,求这30人排队买票,不会出现找不开钱的尴尬局面的不同排列种数(拿相同面值的人调换位置为同一种排列)。
令f(m,n)表示有m个人手持50元钞票,n个人手持100元钞票的不同排列总数
(1)n=0,所有人都是拿50元的钞票,则f(m,0)=1;
(2)m<n,肯定会出现找不开钱的尴尬局面,f(m,n)=0;
(3)我们考虑最后第m+n个人站在第m+n-1个人的后面,第m+n个人手持钞票有以下两种情况:
1.第m+n人手里拿着100元的毛爷爷,则在他之前有m个人手持50元,n-1个人手持100元,那么这种情况有f(m,n-1)种;
2.第m+n人手里拿着50元的毛爷爷,则在他之前有m-1个人手持50元,n个人手持100元,那么这种情况有f(m-1,n)种;
由加法原理可知:
f(m,n)=f(m,n-1)+f(m-1,n);
一般地,排队购票的递归关系:
f(j,i)=f(j-1,,i)+f(j,i-1) (0<i<=n,0<j<=m)
初始条件:
当j<i时,f(j,i)=0;
当i=0时,f(j.i)=1;
以下分别用递归和递推实现:
long f(int j,int i)
{
long y;
if(i==0) y=1;
else if(j<i) y=0;
else y=f(j-1,i)+f(j,i-1);
return y;
}
#include <stdio.h>
void main()
{
int m,n;
printf(" input m,n:");
scanf("%d,%d",&m,&n);
printf(" f(%d,%d)=%1d\n",m,n,f(m,n));
}
----------
递推:
#include <stdio.h>
void main()
{
int m,n,i,j;
long f[100][100];
printf(" input m,n:");
scanf("%d,%d",&m,&n);
for(j=1;j<=m,j++) //这五句用来确定初始条件
f[j][0]=1;
for(j=0;j<=m;j++)
for(i=j+1;i<=m+1;i++)
f[j][i]=0;
for(i=1;i<=n;i++)
for(j=i;j<=m;j++) //当j<i时f[j][i]=0,所以这里从j>=i开始
f[j][i]=f[j-1][i]+f[j][i-1]; //实施递推
printf(" f(%d,%d)=%1d\n",m,n,f(m,n));
}
递归设计中每调用一次f(j,i)就要调用另外两个函数f(j-1,,i)和f(j,i-1),调用结构是二叉树,取m,n的最小值为n,则时间复杂度是O(4^n),为指数时间,当n比较大时,递归无法实现。而递推应用二重循环完成,时间复杂度O(n^2),显然递推效率高于递归。
引申,带条件的递归:假设第5位是手持 50元,第8位手持100元。其他要求和上面一样。
因为第5位是手持50元,又因为5只能由4个50,1个100 或者3个50,2个100这两种情况,既然第5个人只有一种可能(手持50),
if(j==4&&i==1||j==3&&i==2)
f(j,i)=f(j-1,i)
同理
if(j==7&&i==1||j==6&i==2||j==5&&i==3)
f(j,i)=f(j,i-1)
递归函数:
long f(int j,int i)
{
long y;
if(i==0&&j<8) y=1;
else if(j<i||i==0&&j>=8) y=0;
else if(j==4&&i==1||j==3&&i==2)
y=f(j-1,i);
else if(j==7&&i==1||j==6&i==2||j==5&&i==3)
y=f[j,i-1);
else y=f(j-1,i)+f(j,i-1);
return y;
}