递归
递归是一种针对使用简单的循环难以编程实现的问题 ,提供优雅解决方案的技术
使用递归就是使用递归方法编程那个,递归方法就是设计调用自身的方法。递归是一种很有用的程序设计技术。
计算阶乘
递归方法是直接或间接调用自身的方法
0! = 1;
n! = n * (n - 1)!; n > 0
对给定的n如何求n!呢?由于已经知道了0!=1,而1!=1 * 0!,因此很容易求得1!。假设已经知道(n-1)!,使用n!=n * (n-1)!就可以立即得到n!。这样,计算n!的问题就简化为计算(n-1)!。当计算(n-1)!时,可以递归地应用这个思路直到n递减到0.
public static int factorial(int n){
if(n == 0)
return 1;
else
return n * factorial(n - 1);
}
一个递归调用可以导致很多的递归调用,因为这个方法继续把每个子问题分解成新的子问题。要终止递归方法,问题最后必须达到一个终止条件。当问题达到这个终止条件时,就将结果返回给调用者。然后调用这=者进行计算结果并将结果返回给他的调用者。这个过程持续进行,直到结果传回到原始的调用者为止。
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner input = new Scanner(System.in);
System.out.println("请输入一个非负整数");
int n = input.nextInt();
System.out.println(n+"阶乘的结果是"+factorial(n));
}
public static int factorial(int n){
if(n == 0)
return 1;
else
return n * factorial(n - 1);
}
递归简单来说是自己调用自己,调用一个更小的自己,调用到规定的最小值时停止调用返回结果,用下面一个图来讲解比较简单
斐波那契数
著名的兔子繁殖数列:假如有一对兔子,从出生后第三个月起每个月都生一对兔子,小兔子长到三个月后又生一对兔子,如果兔子都不死,问几个月后的兔子总数是多少?
斐波那契数列从0和1开始,之后的每个数都是序列中前两个数的和(以前的兔子加上新生的兔子)。
数列:0 1 1 2 3 5 8 13 21 34 55 89
下标:0 1 2 3 4 5 6 7 8 9 10 11
由此可以简单的用下面的代码描述
public static int fib(int index) {
if(index == 0) {
return 0;
}else if(index == 1) {
return 1;
}
else {
return fib(index - 1)+fib(index - 2);
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner input = new Scanner(System.in);
System.out.println("请输入一个非负整数");
int index = input.nextInt();
System.out.println(n+"个月后兔子的数量是"+fib(index)+"只");
}
public static int fib(int index) {
if(index == 0) {
return 0;
}else if(index == 1) {
return 1;
}
else {
return fib(index - 1)+fib(index - 2);
}
}
斐波那契数列调用图像和上面的递归调用图像很相像,但是在方法的最后一行(return fib(index - 1)+fib(index - 2))方法是调用了两次自己,所以斐波那契数列调用的次数要比传统的递归数列 调用的次数要多一些,如果传入的参数越大,调用的次数就越多。
使用递归解决问题
递归的特点:
-
这些方法使用if-else或switch语句来导向不同的情形
-
一个或多个基础情形(最简单的情形)用来停止递归
-
每次递归调用都会简化原始问题,让它不断地接近基础情形,直到成为基础情形为止
因为递归调用自身和逐渐分解的特点,我们可以用它来解决一些特殊的问题,比如回文字符串
检查回文字符串可以分解为两个问题
- 检查字符串的第一个字符和最后一个字符是否相等
- 忽略字符串本身首尾两端返回一个新的字符串,然后执行第一步
public static boolean isPalindrome(String string) {
if(string.length()<=1)//设立基础情形,1或者0,代表true或者false
return true;
else if(string.charAt(0) != string.charAt(string.length() - 1))
return false;
else//调用自身,string.sunstring()方法返回字符串的指定的字串
return isPalindrome(string.substring(1,string.length()-1));
}
递归辅助方法
通过针对要解决的初始问题的类似问题定义一个递归方法,来找到初始问题的解决方法叫做递归辅助方法。
对于上文的回文串问题,如果不返回一个字符串而为了高效使用下标的话可以使用递归辅助方法。
public static boolean isPalindrome(String string) {
return isPalindrome(string,0,string.length()-1);
}
private static boolean isPalindrome(String string,int low,int high) {
if(high <= low) {
return true;
}else if(string.charAt(low)!=string.charAt(high)) {
return false;
}else {
return isPalindrome(string,low + 1,high - 1);
}
}
这个方法通过不断缩短字符串的子串,返回是否为回文串。在递归程序设计中定义第二个方法来接收附加的参数是一个常用的技巧,这样的方法称为递归辅助方法。
辅助方法常用于关于字符串和数组问题的递归解决方案上是非常有用的。
递归选择排序
利用选择排序法运用递归进行对数组的排序
排序步骤及思路如下:
- 找出列表中的最小数,然后将它与第一个数交换
- 忽略第一个数,对余下的数组进行上一步操作
public static void sort(double[]list) {
sort(list,0,list.length -1);
}
public static void sort(double[] list,int low,int high) {
if(low < high) {
int indexOfMin = low;//设置最小值的下标
double min = list[low];//将第一个数取出,准备与其他数比较
for(int i = low + 1;i <= high;i++) {//使用循环遍历每一个元素
if(list[i] < min) {//使每一个元素与第一个数相比较
min = list[i];//如果有元素比第一个元素小,则将较小的数赋给第三个数,留作以后的位置交换,再进行循环,寻找更小的元素
indexOfMin = i;//将较小的数的下标取出,帮助交换元素
}
}
list[indexOfMin] = list[low];//用第三方参数交换两个元素的位置
list[low] = min;
sort(list,low+1,high);//交换完成,最小元素下标+1,忽略已经找出的最小值,在剩下的数组中进行下次交换
}
}
递归二分查找
使用二分查找法必须要先排好序才能使用,排序后利用递归的具体查找步骤如下(此处默认排序先小后大)
- 如果关键字比中间元素小,那么只在前一半数组元素中查找
- 如果关键字与中间元素相等,则匹配成功,查找结束
- 如果关键字比中间元素大,那么只需在后一半数组元素中查找
- 利用递归重复上述步骤,直到查找结束
public static int recursiveBinarySeach(int[] list,int key) {
int low = 0;//目标数组首端元素下标
int high = list.length - 1;//目标数组末端元素下标
return recursiveBinarySeach(list,key,low,high);
}
public static int recursiveBinarySeach(int[] list,int key,int low,int high) {
if(low > high) {//数组首端元素下标大于末端元素下标,数组发生错误,返回-1
return -1;
}
int mid = (low + high) / 2;//指定数组中间元素下标
if(key < list[mid]) {//关键字小于数组中间元素,在前半部分数组中查找
return recursiveBinarySeach(list,key,low,mid-1);//目标数组首端元素不变,数组中间元素指定为末端元素
}else if(key == list[mid]) {//查找到中间元素为目标元素,返回指定下标,递归结果传递值调用端,递归结束
return mid;
}else {//关键字大于数组中间元素,在目标数组后半部分查找
return recursiveBinarySeach(list,key,mid + 1,high);//目标数组末端元素不变,数组中间元素指定为首端元素
}
}
获取目录的大小
对于具有递归结构的问题,采取递归的方法求解更加高效
目录大小指该目录下所有文件大小之和。一个目录可能包含文件和子目录,那么它们的大小可以用:
size(d)=size(f1)+size(f2)+···size(fn)+size(d1)+size(d2)+···size(dn)
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner input = new Scanner(System.in);
System.out.println("请输入正确目标文件路径");
String directory = input.nextLine();
System.out.println(directory+"的大小为"+getSize(new File(directory))+"bytes");
}
public static long getSize(File file) {
long size = 0;
if(file.isDirectory()) {//测试此抽象路径名表示的是否是一个目录
File[] files = file.listFiles();//建立一个文件数组,将所有文件路径放入,逐个进行运算
for(int i = 0;files != null && i<files.length;i++) {//设立循环,直到目录遍历剩余文件为空,保证每个文件都被扫描
size += getSize(files[i]);//返回每个文件大小,都加入size里面
}
}else {
size += file.length();//若指定路径不是一个目录,则返回它的大小(非目录返回大小为0),方法结束
}
return size;
}