Java算法之递归算法
递归算法概述
程序调用自身的编程技巧称为递归( recursion)。 一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。一般来说,递归需要有边界条件、递归前进段和递归返回段。当边界条件不满足时,递归前进;当边界条件满足时,递归返回。
递归算法思想
递归算法是把问题转化为规模缩小了的同类问题的子问题。然后递归调用函数(或过程)来表示问题的解。在C语言中的运行堆栈为他的存在提供了很好的支持,过程一般是通过函数或子过程来实现。
递归算法:在函数或子过程的内部,直接或者间接地调用自己的算法
递归算法算法原理
上图是递归算法的堆栈实现,fact()子函数每次被调用系统都会为这个函数创建一个新的过程,每个过程都是独立的,每个n都不同,都有自己存储空间,只有每个被调过程完成时,才返回上一个被调过程。
递归算法解决问题的特点:
(1) 递归就是在过程或函数里调用自身。
(2) 在使用递增归策略时,必须有一个明确的递归结束条件,称为递归出口。
(3) 递归算法解题通常显得很简洁,但递归算法解题的运行效率较低。所以一般不提倡用递归算法设计程序。
(4) 在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等。所以一般不提倡用递归算法设计程序。
递归算法常用示例
求整数阶乘
源码:
/**
* 递归算法求整数阶乘
*
* @author Administrator
*
*/
public class Recursion_Factorial {
public long recursion(int n) {
long value = 0;
if (n == 1 || n == 0) {
value = 1;
} else if (n > 1) {
value = n * recursion(n - 1);
}
return value;
}
public static void main(String[] args) {
Recursion_Factorial rf = new Recursion_Factorial();
long factSum = rf.recursion(5);
System.out.println("5的阶乘为:"+factSum);
}
}
测试源码结果:
5的阶乘为:120
斐波那契数列
题目:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少?
源码:
public class Recursion_Fibonacci {
public static final int MONTH = 15;
public static void main(String[] args) {
long f1 = 1L, f2 = 1L;
long f;
for (int i = 3; i < MONTH; i++) {
f = f2;
f2 = f1 + f2;
f1 = f;
System.out.print("第" + i + "个月的兔子对数: "+ f2+"\n");
}
}
}
测试源码结果:
第3个月的兔子对数: 2
第4个月的兔子对数: 3
第5个月的兔子对数: 5
第6个月的兔子对数: 8
第7个月的兔子对数: 13
第8个月的兔子对数: 21
第9个月的兔子对数: 34
第10个月的兔子对数: 55
第11个月的兔子对数: 89
第12个月的兔子对数: 144
第13个月的兔子对数: 233
第14个月的兔子对数: 377
汉诺塔问题
题目:有一个梵塔,塔内有三个座A、B、C,A座上有诺干个盘子,盘子大小不等,大的在下,小的在上(如图)。
把这些个盘子从A座移到C座,中间可以借用B座但每次只能允许移动一个盘子,并且在移动过程中,3个座上的盘
子始终保持大盘在下,小盘在上。
问题解析:
当只有一个盘子的时候,只需要从将A塔上的一个盘子移到C塔上。
当A塔上有两个盘子是,先将A塔上的1号盘子(编号从上到下)移动到B塔上,再将A塔上的2号盘子移动的C塔上,最后将B塔上的小盘子移动到C塔上。
当A塔上有3个盘子时,先将A塔上编号1至2的盘子(共2个)移动到B塔上(需借助C塔),然后将A塔上的3号最大的盘子移动到C塔,最后将B塔上的两个盘子借助A塔移动到C塔上。
当A塔上有n个盘子是,先将A塔上编号1至n-1的盘子(共n-1个)移动到B塔上(借助C塔),然后将A塔上最大的n号盘子移动到C塔上,最后将B塔上的n-1个盘子借助A塔移动到C塔上。
综上所述,除了只有一个盘子时不需要借助其他塔外,其余情况均一样(只是事件的复杂程度不一样)。
源码:
/**
* 递归算法 解决汉诺塔问题
*
* @author Administrator
*
*/
public class Recursion_HanoiTower {
private final static String from = "盘子B";
private final static String to = "盘子C";
private final static String mid = "盘子A";
public static void main(String[] args) {
String input = JOptionPane.showInputDialog("请输入你要移动的盘子数");
int num = Integer.parseInt(input);
Recursion_HanoiTower.move(num, from, mid, to);
}
private static void move(int num, String from2, String mid2, String to2) {
if (num == 1) {
System.out.println("移动盘子编号:" + num + " 从" + from2 + "到" + to2);
} else {
move(num - 1, from2, to2, mid2);
System.out.println("移动盘子编号:" + num + " 从" + from2 + "到" + to2);
move(num - 1, mid2, from2, to2);
}
}
}
测试源码结果:
移动盘子编号:1 从盘子B到盘子C
移动盘子编号:2 从盘子B到盘子A
移动盘子编号:1 从盘子C到盘子A
移动盘子编号:3 从盘子B到盘子C
移动盘子编号:1 从盘子A到盘子B
移动盘子编号:2 从盘子A到盘子C
移动盘子编号:1 从盘子B到盘子C
文字表达效果不理想,网上找了个图,帮助理解。
也可以参考在C中使用:
经典递归解决汉诺塔!
遍历文件子目录
在Android开发中遍历文件夹下文件通常使用递归算法。提供一个项目中的遍历目录下文件和文件夹,以供参考。
源码:
/**
* 遍历目录下文件和文件夹
*/
ArrayList<String> certlists = new ArrayList<>();
private ArrayList<String> refreshCertLists(String certificatePath) {
File certfile = new File(certificatePath);
File[] files = certfile.listFiles();
if (files == null) {
return null;
}
for (int i = 0; i < files.length; i++) {
if (files[i].isDirectory()) {
refreshCertLists(files[i].getAbsolutePath());
} else {
String fileName = files[i].getName();
if (fileName.trim().toLowerCase().endsWith(".cer")) {
String strFileName = files[i].getAbsolutePath().toLowerCase();
Log.i("TAG", "strFileName1--->" + strFileName);
String filesDir = context.getFilesDir().getPath();//获取Files目录地址
filesDir = filesDir.substring(0, filesDir.length() - 6);
strFileName = strFileName.substring(0, strFileName.length() - 14);
strFileName = strFileName.substring(filesDir.length());
// Log.i("TAG", "strFileName--->" + strFileName);
// Log.i("TAG", "strFileNameLen--->" + strFileName.length());
strFileName = strFileName.substring(9);
Log.i("TAG", "strFileName--->" + strFileName);
// Log.i("TAG", "strFileNameLen--->" + strFileName.length());
certlists.add(strFileName);
}
}
}
Log.i("TAG", "certlists>>>>" + certlists);
return certlists;
}
总结
递归算法通常重复调用自身,递归次数过多容易造成栈溢出等各种问题,如果考虑软件性能,要慎用递归算法。