1.概述
程序调用自身的编程技巧称为递归( recursion)
2.递归思想
- 递归就是方法里调用自身
- 在使用递归策略时,必须有一个明确的递归结束条件,称为递归出口
- 递归算法代码显得很简洁,但递归算法解题的运行效率较低。所以不提倡用递归设计程序。
- 在递归调用的过程中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等,所以一般不提倡用递归算法设计程序。
3.递归的三个条件
- 边界条件
- 递归前进段
递归返回段
当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
4.递归的优点和缺点
优点
- 代码更简洁清晰,可读性更好
- 在树的前序,中序,后序遍历算法中,递归的实现明显要比循环简单得多
缺点
- 由于递归需要系统堆栈,所以空间消耗要比非递归代码要大很多
- 调用栈可能会溢出,其实每一次函数调用会在内存栈中分配空间,而每个进程的栈的容量是有限的,当调用的层次太多时,就会超出栈的容量,从而导致栈溢出。->性能
- 递归中很多计算都是重复的,由于其本质是把一个问题分解成两个或者多个小问题,多个小问题存在相互重叠的部分,则存在重复计算,如fibonacci斐波那契数列的递归实现。->效率
- 递归由于是函数调用自身,而函数调用是有时间和空间的消耗的:每一次函数调用,都需要在内存栈中分配空间以保存参数、返回地址以及临时变量,而往栈中压入数据和弹出数据都需要时间。->效率
5.下面通过两个示例程序来说明:
5.1使用Java代码求5的阶乘。(5的阶乘=5*4*3*2*1)
package org.android.test;
/**
* 计算5的阶乘(result = 5*4*3*2*1)
*/
public class Test01 {
public static void main(String[] args) {
System.out.println(f(5));
}
//递归的方法
public static int f(int n) {
if (1 == n)
return 1;
else
return n*(n-1);
}
}
此题中,按照递归的三个条件来分析:
(1)边界条件:阶乘,乘到最后一个数,即1的时候,返回1,程序执行到底;
(2)递归前进段:当前的参数不等于1的时候,继续调用自身;
(3)递归返回段:从最大的数开始乘,如果当前参数是5,那么就是5*4,即5*(5-1),即n*(n-1)
使用Java代码求数列:1,1,2,3,5,8……第40位的数
[java]
package org.wxp.recursion;
5.2求数列:1,1,2,3,5,8……第40位的数
public class Test02{
public static void main(String[] args) {
System.out.println(f(6));
}
public static int f(int n ) {
if (1== n || 2 == n)
return 1;
else
return f(n-1) + f(n-2);
}
}
此题的突破口在:从第3位数开始,本位数是前两位数的和。要计算第多少位的值,那么就需要将位数作为参数传进方法进行计算。
(1)首先,当位数为1和2时,当前返回的值应该是1;
(2)然后,当位数为3时,返回值应该=2=1+1;
当位数为4时,返回值=3=2+1;
当位数为5时,返回值=5=3+2;
当位数为6时,返回值=8=5+3;
……
(3)由(2)得知,大于等于3的情况下,当前位数(n)的数值=f(n-1)+f(n-2)
6.和循环相比
6.1递归算法:
优点:代码简洁、清晰,并且容易验证正确性。(如果你真的理解了算法的话,否则你更晕)
缺点:运行需要多次调用,增加额外的堆栈处理(还有可能出现堆栈溢出的情况),会对执行效率有一定影响。但是,对于某些问题,如果不使用递归,那将是极端难看的代码。
6.2循环算法:
优点:速度快,结构简单。
缺点:并不能解决所有的问题。有的问题适合使用递归而不是循环。如果使用循环并不困难的话,最好使用循环。
7.Android递归遍历文件夹中指定格式文件
public ArrayList<String> refreshFileList(String strPath) {
String filename;//文件名
String suf;//文件后缀
File dir = new File(strPath);//文件夹dir
File[] files = dir.listFiles();//文件夹下的所有文件或文件夹
if (files == null)
return null;
for (int i = 0; i < files.length; i++) {
if (files[i].isDirectory()){
System.out.println("---" + files[i].getAbsolutePath());
refreshFileList(files[i].getAbsolutePath());//递归文件夹!!
}else {
filename = files[i].getName();
int j = filename.lastIndexOf(".");
suf = filename.substring(j+1);//得到文件后缀
if(suf.equalsIgnoreCase("amr"))//判断是不是msml后缀的文件
{
String strFileName = files[i].getAbsolutePath().toLowerCase();
wechats.add(files[i].getAbsolutePath());//对于文件才把它的路径加到filelist中
}
}
}
return wechats;
}
8.java中关于指定文件夹下所有文件的遍历
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public class ListDirectory {
public static void showDirectory(File file){
File[] files = file.listFiles();
for(File a:files){
System.out.println(a.getAbsolutePath());
if(a.isDirectory()){
showDirectory(a);
}
}
}
public static void main(String[] args) {
File file = new File("E:\\aaa");
showDirectory(file);
}
}
9.接下来个递归的题目:
第一个人10岁,第二个人比第一个人大2岁,以此类推,用递归方式算出第10个人年龄多大?
package com.test;
public class ComputeAge {
/**
* 从递归的三个条件出发:
* 1、递归边界 第10个人
* 2、递归前进段 每次大2岁
* 3、递归返回 return age
最终自己调用自己实现
*/
public static int getComputeAge(int n) {
int age = 0;
if (n == 1) {
age = 10;
} else {
age = getComputeAge(n - 1) + 2;
}
return age;
}
public static void main(String[] args) {
System.out.println(getComputeAge(10));
}
}
总的来说,递归在算法中的使用场景较多,大家慢慢体会。