简析递归
递归是编程过程中比较重要,也是比较简单的算法之一,简单的递归思路,往往比较容易理解,而略微复杂的递归则容易让人头痛。
递归主要分两种
- 直接递归
直接递归函数里面调用本身这个函数,实现递归 - 间接递归。
间接递归是两个,或两个以上的函数互相调用,正好形成一个循环,例如:A调用B,而B又调用A,在某种条件下,会实现间接递归。自然也可以是多个函数组成间接递归,不过考虑到逻辑问题,一般都不会这么绕。
直接递归
public int Factorial( n)
{
if(n==0||n==1)
return 1;
else
return n * Factorial(n-1);
}
这是一个直接递归求阶乘的例子,自然求阶乘用递归不是一个好的方法,完全可以用循环来代替。由于这是一个单层次的问题,而用递归把每一次阶乘都当作一个层次,而每一层都只有个元素,有些得不偿失
看一下这个遍历文件夹的例子
public static void getFileDir(File file){
//如果是文件夹,那就是直接递归,如果不是那就直接输出文件名
if(file.isDirectory()) {
File[] files=file.listFiles();
for(int i=0;i<files.length;i++){
main.getFileDir(files[i]);
}
}
else{
System.out.println(file.getAbsolutePath());
}
}
思路相当的简单,但是你只要给一个文件夹目录,就可以遍历出这个文件夹下面的所有文件名,而每一层都是一个目录,同一层下有多个元素,就像一颗树一样。
遍历的思想就体现出来了,树形结构的遍历不就是递归吗!而线性结构的遍历就是循环.
说一下递归的深度问题,有时候需要获得递归的深度,好分组,像在爬虫中就是使用递归的思想,但是如何分类呢?没错,就是根据深度来分,但如何知道这是第几层呢(深度)。来让我们小改一下程序
public static void getFileDir(File file,int deep){
if(file.isDirectory()) {
File[] files=file.listFiles();
for(int i=0;i<files.length;i++){
main.getFileDir(files[i],deep+1);
}
}
else{
System.out.println(file.getAbsolutePath()+" 深度为:"+deep);
`
}
}
没错仅仅加了一个参数,deep,传入1就可以了,多么简单,第二层输出的是deep+1,第三层就是((deep+1)+1),那么第一层的deep会变吗?没有变化!第二层永远都会是二,我们没有对deep做过赋值,只是改变传入的参数而已(在前面遇到有不理解此问题的朋友,所有特意说明一下)
接下来看一下间接递归的例子
var time = 24*60*60*2; //倒计时两天的时间,自己设定
//输出信息
function begin(){
var today=new Date()
var day =today.getDate()
var dat=today.getMonth()
var future=day+2
document.getElementById('now').innerHTML="现在时间"+dat+"月"+day+"倒计时开始"
leasttime()
document.getElementById("future").innerHTML="预计结束时间"+dat+"月"+future
}
//时间倒计时函数
function leasttime(){
var ho=time/(60*60);
var mi=(time/60)%(60)
var se=time%60
mi=parseInt(mi)
ho=parseInt(ho)
ho=checkTime(ho)
se=checkTime(se)
mi=checkTime(mi)
time-=1;
document.getElementById("last").innerHTML=ho+":"+mi+":"+se
//倒计时结束
if(time==0){
// //重置计时器 ,再次开始计时
time=30;
begin()
}
setTimeout("leasttime()",1000);
}
//将时间的格式转化一下
function checkTime(i)
{
if (i<10)
{i="0" + i}
return i
}
这是一个使用javascript完成的一个循环倒计时的计时器,由于在javascript中是解释执行的,而且由于在主线程中,无法使用死循环执行循环倒计时的效果,浏览器会卡死并报错。
总结一下递归的构成条件
- 子问题须与原始问题为同样的事,且更为简单;
- 不能无限制地调用本身,须有个出口,化简为非递归状况处理。
递归的使用
1 遍历树形结构
2. 像javascript中间接递归的特例。
递归算法一般用于解决三类问题:
1.数据的定义是按递归定义的。(Fibonacci函数)
2.问题解法按递归算法实现。
这类问题虽则本身没有明显的递归结构,但用递归求解比迭代求解更简单,如Hanoi问题。
3.数据的结构形式是按递归定义的。
如二叉树、广义表等,由于结构本身固有的递归特性,则它们的操作可递归地描述。
递归的缺点:
递归算法解题相对常用的算法如普通循环等,运行效率较低。因此,应该尽量避免使用递归,除非没有更好的算法或者某种特定情况,递归更为适合的时候。在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。
如何设计递归算法
1.确定递归公式
2.确定边界(终了)条件
练习:
用递归的方法完成下列问题
1.求数组中的最大数
2.1+2+3+…+n
3.求n个整数的积
4.求n个整数的平均值
5.求n个自然数的最大公约数与最小公倍数
6.有一对雌雄兔,每两个月就繁殖雌雄各一对兔子.问n个月后共有多少对兔子
7.已知:数列1,1,2,4,7,13,24,44,…求数列的第 n项.
练习:
1.计算ackerman函数值:
n+1 m=0
ack(m,n)={ ack(m-1,1) m<>0,n=0
ack(m-1,ack(m,n-1)) m<>0,n<>0
求ack(5,4)