"一个函数调用其自身,就是递归."递归的概念简单的有点让人摸不着头脑.
我们知道一个函数本质上是为了能够让调用者通过传递一个口令(也即参数),让其实现一个固定的功能.
那么递归就是自己既充当了主人,又充当了仆人的角色.他通过不断的使用同一个功能来解决问题,直到取得突破点.这个突破点往往是一些隐藏的已知条件.同时不难发现一般这样的问题可以划分为能用同一个功能完成的有限个子问题.
例如求5的阶乘.我们知道5!=5*4!,那么同样又要求4!,4!=4*3!,那么同样又要求3!,3!=3*2!,那么同样又要求2!,2!=2*1!.我们知道1!等于1.这个就是突破点.知道了1!就知道了2!,从而又知道了3!,最终就能够求出5!.而这其实也是阶乘的定义.
我们可以编写一个求阶乘的函数.返回值为所求的结果,参数为你想求谁的阶乘.
int Fac(int n){//忽略0
//首先判断n是否为1,如果是1,那么直接返回1.
if(n==1) return 1;
//若不为1,就调用其自身,先求Fac(n-1),然后再乘以n
else return n*Fac(n-1);
}
除此之外,还有一个典型的递归问题,汉诺塔问题:古代有一个方塔,塔内有三个座A,B,C,A座上有64个盘子,盘子大小不等.大的在下,小的在上.有一个和尚先把这64个盘子从A座移到C座,并且在移动过程中,3个座上的盘子适终保持大盘在下,小盘在上.在移动过程中可以借助B座,要求输出移动的步骤.
按照递归的思想,我们想将n个盘子,从A座借助B座移动到C座,并且要保证始终大盘在下,小盘在上.可以考虑:先将n-1个盘子从A移到B,再将剩下的一个最大的从A移到C,最后将B座上的n-1个盘子从B移到C.是不是也想求阶乘那样5!去求4!,4!去求3!...直至1!=1.那么,64个盘子的移法,先去移63,63个盘子的移法,先去移62,直至1个盘子的移法,就是直接将盘子从A移到C.
同样我们可以编写一个汉诺塔移法的函数.参数为盘子的个数以及三个底座A,B,C.
void Hanoi(int n,char src,char mid,char dest){
if(n==1){//只有一个盘子,直接从A->C
cout<<src<<"->"<<dest<<endl;
return ;
}
Hanoi(n-1,src,dest,mid);//先将n-1个盘子从src移动到mid
cout<<src<<"->"<<dest<<endl;
Hanoi(n-1,mid,src,dest);//先将n-1个盘子从src移动到mid
return ;
}
回到阶乘问题,我想说,求阶乘直接用一重循环不就ok了吗,为啥还要用递归.数字大的时候还容易引发诸多问题.
int result=1;
for(int i=n;i>0;i--)
result*=i;
return result;
那么多重循环呢?
例如著名的n皇后问题:输入整数n,要求n个国际象棋的皇后,摆在n*n的棋盘上,互相不能攻击,输出全部方案.当然可以使用八重循环,但是怎么想都觉得有点low.
以POJ上的OJ题为例:
首先思考我们摆放皇后的方法是否一样:摆放第k个(第k行)皇后的时候,我们要考虑的是当前摆放的位置是否与前k-1个的皇后冲突.如果冲突测试下一个位置.如果不冲突,则记录此位置为第k个皇后的列号.而且在摆放的时候最好从第一行开始摆,摆到第n行的时候就结束.(当然,你要倒着摆也行.按从小到大顺序更符合人的一般思维)
首先编写主程序:
cin>>N;
NQueen(0);//从第0行开始摆,按从小到大顺序
return 0;
然后编写NQueen函数
void NQueen(int k){//在0-k-1行摆好的情况下,摆第k行
int sum=0,i;
if(k==N){//N个皇后已经摆好,直接输出.注意皇后有第0个,到k=N时,说明0到N-1已经摆完,也即任务完成
for(i=0;i<N;i++)
cout<<queenPos[i]+1<<" ";//queenPos用于存放算好的皇后位置
cout<<endl;
sum++;//记录有几种摆法
return ;
}
for(i=0;i<N;i++){//逐个尝试第k个皇后的位置,从第0列到第k-1列.
int j;
for(j=0;j<k;j++){//和已经摆好的k个皇后的位置(即j)作比较(因为有0,所以注释中不写为k-1),看是否冲突
if(queenPos[j]==i||abs(k-j)==abs(queenPos[j]-i)){//因为不在同一行,所以只考虑列冲突和斜线冲突(列相减绝对值和行相减绝对值)
break;//冲突,测试下一个位置
}
}
if(j==k){//当前所选的位置i不冲突(即与已经摆好的k个皇后都不冲突)
queenPos[k]=i;//记录所摆放位置
NQueen(k+1);//继续摆放下一个,即递归,相同摆法
}
}
}
我想现在我们可以来思考为什么算法要引入递归思想:
1.解决本身就是递归定义的问题,比如说阶乘.
2.将问题分解为规模更小的子问题求解,然后发现突破点.
3.递归可以替代多重循环.(n皇后问题)
最后我想留一个问题,递归思想和动归DP又有什么关联呢,还是什么关系都没有?或者觉得意犹未尽的看看姊妹篇:再谈递归.
在本专栏中,有枚举,回溯,递归,动归,深搜,广搜,贪心等详细讲解,有兴趣的朋友可以看看.