数据结构与算法之两句话讲懂递归`
声明:并不是全文就两句话,而是全文围绕两句话。
人理解循环,神理解递归。
这句话是在网上偶尔看到的,出处在哪已经找不到了。当时对这句话深以为然,因为初次接触递归时,感觉递归非常的难以理解。总觉得递归非常的神秘莫测,非常的抽象。其实不然,递归本质上是一种特殊的循环写法。可以这样说,只要你懂得循环的思想,你就能把握递归的精髓。“人理解循环,神理解递归。”你要知道“神”都是由“人”修行而来,所以当你掌握基本循环思想,再通过练习进阶,你会发现递归就是将问题拆分成子问题,然后循环解决子问题。
递归,递归,游子当归。
额…这句话是我编的…,但是可以这样说只要你get到这句话的精髓,你的修为就会进一大步。所以我建议你拿起小本本记下这一句话。那这句话到底有啥玄机?玄机就在于“当归”二字。即递归得有个边界。
干巴巴的讲这两句话,效果不好,还是来点代码例子实际说明一下。
经典例子:求n的阶乘。
代码:
int factorial(int n){
if(n==0){
return 1;# 当游子远游到“0”时,就该“归了”(即return)
}
else{
return factorial(n-1)*n;#factorial(n-1)是游子,这个游子不断的循环,每循环一次就减1
}
}
感受完上述的例子,可能还有疑问:没看出来递归和循环的啥关系,递归的写法与循环的写法一点也不一样啊。那么再来一个例子;小青蛙跳台阶问题,现在有100级台阶,小青蛙目前在第0级,并且小青蛙每次只能跳一级或者跳两级。小青蛙跳到100级时,共有多少种跳法?
分析:比较“直”的思路就是,我们求出第一级、第二级、第三极、、、、有多少种,然后循环的累加。代码:
int jump_1(int a[]){
a[0]=1;
a[1]=1;
for (int i=2;i<=100;i++){
a[i]=a[i-2]+a[i-1];
}
return a[100];
}
而递归就是,要求第100级有多少种,那就要求第99级和第98级,而要求第99级和第98级。则要求第97级、、、、以此递推。代码:
int jump_2(n){
if(n==1||n==0){
return 1;
}
else{
return jump_2(n-1)+jump_2(n-2);
}
}
所以你如果是采用从下至顶的思路,写出来就是循环。如果采用从顶至下的思路,写出来就是递归。
理论上只要能用递归的话,就能用循环写出来,只不过可能会使用栈。递归调用的是系统栈,所以我们可以创建用户栈来用循环写出。
当然,这两句并不能让你掌握递归,而是让你对递归有个初步的认识。so,建议做几道oj题来辅助食用。
一,全排列问题:输入一个数,请输出所有的排列情况。要求按字典序从小到大。
#include<cstdio>
const int maxn=10000;
int put[maxn];#输出的全排列的序列。
bool vis[maxn]={false};
int length;
void full_permutaion(int n){
if(n==length){
for(int i=0;i<length;i++){
printf("%d ",put[i]);
}
printf("\n");
return;#游子当归
}
for(int i=0;i<length;i++) {
if(vis[i]!=1){
vis[i]=1;
put[i]=i;
full_permutaion(n+1);
vis[i]=0;
}
}
}
int main(){
scanf("%d",&length);
full_permutaion(0)
return 0;
}
二,n皇后问题:
n*n的地图中,放置n个皇后。要求每一行、列只能有一皇后。并且两个或多个皇后不能存在同一条对角线上。问有多少种?
#include<cstdio>
const int maxn=10000;
int put[maxn];#输出的皇后排列的序列。
bool vis[maxn]={false};
int cnt=0;
int length;
void n_quen(int n){
if(n==length){
for(int i=0;i<lenrth;i++){
for (int j=i+1;j<length-1;j++){
if(abs(i-j)==abs(put[i]-put[j])){
for(int i=0;i<length;i++){
print("%d",put[i]);
}
printf("\n");
cnt++;
}
}
}
printf("\n");
return;#游子当归}
}
for(int i=0;i<length;i++) {
if(vis[i]!=1){
vis[i]=1;
put[i]=i;
n_quen(n+1);
vis[i]=0;
}
}
}
int main(){
scanf("%d",&length);
n_quen(0)
return 0;
}
改进:当我们完成一个放置方案时,才进行对角线的判断,然后据此筛选出符合要求的。但是这无疑会增加递归搜索的次数。所以我们可以在递归搜索前判断目前放置是否满足所有的要求,若满足则进行递归,不满足则不进行。这个思想也叫“剪枝”,可以提高递归的效率。
代码:
#include<cstdio>
const int maxn=10000;
int put[maxn];#输出的皇后排列的序列。
bool vis[maxn]={false};
int cnt=0;
int length;
void n_quen(int n){
if(n==length){
for(int i=0;i<length;i++){
for(int i=0;i<length;i++){
print("%d",put[i]);
}
printf("\n");
cnt++;
}
}
printf("\n");
return;#游子当归}
}
for(int i=0;i<length;i++) {
if(vis[i]!=1){
for (int j=0;j<length;j++){
if(abs(n-j)!=abs(i-put[j]))#剪枝操作
{
vis[i]=1;
put[i]=i;
n_quen(n+1);
vis[i]=0;
}
}
}
}
int main(){
scanf("%d",&length);
n_quen(0)
return 0;
}
总之 ,这两句话能帮你应付一般情况的递归,要想进阶,还得练习!!!