递归的两种模式
模式一
//递归的过程中在"递"的过程中解决问题
function function_name(Max_argument){if(end_condition){end;}else{solve;function_name(Min_argument);//问题规模逐渐减小}
}
注:位于递归函数前的语句和函数具有顺序性
模式二
//递归的过程中在"归"的过程中解决问题
function function_name(Max_argument){if(end_condition){end;}else{function_name(Min_argument);//问题规模逐渐减小solve;}
}
注:位于递归函数后的语句的执行顺序与原顺序相反;
每一级的递归调用都拥有自己当前的局部变量,所以当调用对象的时候应该注意是调用对象的非引用(复制对象), 以避免修改子对象造成对主问题的影响
调用栈
调用栈:描述函数之间的调用关系,当函数之间相互调用的时候会使用调用栈;
调用栈由多个栈帧组成,每个栈帧记录着一个未运行完的函数; 栈帧中保存着该函数的返回地址以及局部变量;
在递归中,递归函数的每一次的”递进去”,栈帧都会将上个函数的返回地址局部变量保存以便在返回的过程中找得到相应的”回归出来的方向”
通过一个简单的例子说明上述调用顺序问题
//10进制转2进制
void f(int n){if(n==0)return;printf("%d",n%2);//模式1,该模式下输出语句将会是顺序调用f(n/2)
}
//n=4,将输出001,整个过程中先调用printf语句,然后"递进去",通过栈帧进行回归操作最终返回至main函数void f(int n){if(n==0)return;f(n/2)printf("%d",n%2)
//模式2,整个过程中先执行f 函数进行递归,到达递归基时返回执行printf
//整个过程中就是通过栈帧来记录返回过程应该执行的printf,形成"回归出来"解决问题,最终返回至main函数
//n=4,输出100
}
接下来将用斐波那契查找作为典型的例子证明尾递归的重要性:
看下面的一个错误的例子:
#include<iostream>
using namespace std;
int find(int a[],int lo,int hi,int e){int mid=(hi+lo)/2;if(e==a[mid]) return mid;if(e<a[mid]) {cout<<"lo_1="<<lo<<endl;find(a,lo,mid-1,e);}if(e>a[mid]){ find(a,mid+1,hi,e);cout<<"lo_2="<<lo<<endl;}if(lo>hi) {cout<<"lo_3="<<lo<<endl;return 0; }}
int main(){//0,1,2,3,4,5,6,7,8,9 ,10,11,12,13,14,15,16,17 int a[]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18};cout<<find(a,0,17,18);return 0;
}
//斐波那契查找
这个例子证明了尾递归的重要,当前出现的问题是在递归过程中返回值具有随机性,为正确将值返回,记住一条
//递归过程中栈帧的调用,到达递归基的时候返回出正确结果,但是经过栈帧的调用后,正确的返回值返回给上一层调用函数,上一层函数中未寻找到正确的值
//无法返回出正确的值,则返回出去一个随机的值,导致最后答案错误, 正确的做法是将递归过程改为尾递归(return find(....)),直接终止程序(将后续的语句执行停止)
//就此返回出去的值只能是递归基时产生的值(每一层程序终止后所对应的栈帧被清除,最后只保留着递归基中的栈帧)
//原本打算采用else处理,不用return改为尾递归,但是失败了,因为栈栈的缘故,每层的栈帧未清除,并且返回过程中没有满足条件的语句导致没有返回值,最终无法
//完成尾递归的操作
//唉, 理解了递归过程,但是还未深刻理解尾递归的含义,莫大的悲哀,导致出现不该出现的错误
针对上述的操作进行修改
//斐波那契查找
/*
#include<iostream>
using namespace std;
int f[]={0,1,1,2,3,5,8,13,21,34};
// 0 1 2 3 4 5 6 7 8 9 10
int find(int a[],int lo,int hi,int e){int k=0;while((hi-lo)>f[k]) {//斐波那契数列确定当前的黄金分割点k++;// cout<<"k"<<k<<endl;} int mid=lo+f[k-2]-1;//在中点求值出现错误,未正确表示中点位置
// int mid=(lo+hi)>>1; if(lo>=hi) {return 0;}//未命中目标if(e==a[mid]) return mid;if(e<a[mid]) {return find(a,lo,mid-1,e);}if(e>a[mid]) {return find(a,mid+1,hi,e);}
}
int main(){//0,1,2,3,4,5,6,7,8,9 ,10,11,12,13,14,15,16,17 int a[]={1,2,3,4,5,6,7,8,9};cout<<find(a,0,9,8);return 0;
}
递归过程调用栈的使用与调用其他函数没有区别,每一次的函数调用将会形成新的栈帧以保存上一次函数的返回地址和局部变量, 当栈帧中函数体执行完成时将会删除栈帧,处理返回值并修改当前代码行
来自别人的话_很赞同__递归函数尽量不要有返回值或者基本类型的返回值(直接尾递归),如果要求必须有复杂类型返回值,写成两个函数,递归函数不要有返回值,只是在函数中改变复杂类型的值;在驱动程序中定义复杂类型,调用递归函数返回复杂类型