您的位置:首页 > 编程语言 > Java开发

Java基本功练习十一(递归与迭代【汉诺塔、文件大小的显示、递归的辅助方法、尾递归】)

2014-12-15 20:09 906 查看
递归是程序控制的一种代替形式,实质上就是不用循环控制的重复。程序每调用一个方法,系统就要给方法中所有的局部变量和参数分配空间,这就要占用大量的内存,还需要额外的时间来处理这些附加的空间。任何用递归解决的问题,都可以用迭代非递归地解决,所以如果可以较容易迭代实现的,就不要用递归。

递归与迭代的选择:根据要解决的问题的本质和我们对这个问题的理解来决定是用递归还是用迭代。根据经验,选择使用递归还是迭代的原则,就是看它能否给出一个反映问题本质的直观解法。如果迭代的方案是显而易见的,那就使用迭代。

示例一:通过简单的菲波那契数列的递归实现,并以图片展示其实现过程。以此说明内存的耗用情况严重,所以如果不是很难实现的方法,最好不要用递归,而用迭代去实现。

实现的源代码如下:

package Blog;

import java.util.Scanner;

public class blogTryProject{
public static void main(String[]args){
Scanner input = new Scanner(System.in);
System.out.print("Enter a nonnegative integer: ");
int n = input.nextInt();
System.out.println("Fibonacci of "+n+" is "+fib(n));
}
public static long fib(int n){
if(n == 0)
return 0;
else if(n == 1)
return 1;
else
return fib(n -1) + fib(n - 2);
}

}
实现fib(4)的调用过程如下图所示:



Java中,对操作数是从左到右计算的。所以调用Fibonacci数列时,先计算左边的子调用直到返回最后结果,再计算右边的子调用。从上图可以看到,调用过程比较繁琐,并且调用过程没有结束,其占用的栈内存不会被释放,如果调用过程很多,有可能造成栈的溢出!实际上,菲波那契数列可以用循环迭代的方式很容易的实现。

示例二:文件大小的显示。运行程序,输入想要计算文件大小的地址,如c:\Program Files\alipay,然后显示此文件或文件夹的大小。

运行效果如图所示:


分析:此题如果使用迭代,或是其他方法,比较难实现,如果用递归的方法,则可以比较直观的将方法写出来。本问题是求出一个目录的大小。一个目录的大小是指该目录下所有文件大小之和。目录d可能会包含子目录。假设一个目录包含文件f1,f2,...,fm以及子目录d1,d2,...,dn,则可以将文件大小递归的表示为:size(d)=size(f1)+size(f2)+...size(fm)+size(d1)+...size(dn)。File类可以用来表示一个文件或一个目录,并且获取文件和目录的属性。如下是实现代码:

package Blog;

import java.util.Scanner;
import java.io.File;

public class blogTryProject{
public static void main(String[]args){
System.out.print("Enter a directory or a file: ");
Scanner input = new Scanner(System.in);
String directory = input.nextLine();
System.out.println(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;i < files.length;i++)
size += getSize(files[i]);//递归调用
}
else
size += file.length();
return size;
}

}
示例三:汉诺塔的实现。汉诺塔是一个用非递归调用很难实现的例子。但是用递归,则思路清晰,代码书写容易。在这种情况下选择用递归是明智的选择。






编写程序实现:借助C将n个盘子从A移动到B上去。并将移动的步骤显示出来。

运行效果如图所示:


实现的源代码如下:
package Blog;

import java.util.Scanner;
import java.io.File;

public class blogTryProject{
public static void main(String[]args){
Scanner input = new Scanner(System.in);
System.out.print("Enter number of disks: ");
int n = input.nextInt();
System.out.println("The moves are:");
moveDisks(n,'A','B','C');
}
public static void moveDisks(int n,char fromTower,char toTower,char auxTower){
if(n == 1)
System.out.println("Move disk "+n+" form "+fromTower+" to "+toTower);
else{
moveDisks(n - 1,fromTower,auxTower,toTower);
System.out.println("Move disk "+n+" from "+fromTower+" to "+toTower);
moveDisks(n - 1,auxTower,toTower,fromTower);
}
}

}


虽然递归调用很占用内存,在很多难以直接实现的场合,不得不用递归调用,这是无法避免的,但是我们可以尽量采用别的方法去减小内存耗用的影响。比如递归的辅助方法和尾递归。
示例四:递归的辅助方法。以二分查找法的实现为例。

运行效果如图:


实现源代码如下:

package Blog;

import java.util.Scanner;
import java.io.File;

public class blogTryProject{
public static void main(String[]args){
Scanner input = new Scanner(System.in);
System.out.print("Enter the number of num : ");
int n = input.nextInt();
int[] num = new int
;
num = create(n);
java.util.Arrays.sort(num);
for(int u:num)
System.out.print(u+" ");
System.out.println();
int key = num[n-2];
int position = BinarySearch(num,key);//调用二分查找
System.out.println(key+" 在数组中的位置是 "+position);
}
public static int BinarySearch(int[] num,int key){
return BinarySearch(num,key,0,num.length - 1);//产生辅助的方法
}
public static int BinarySearch(int[] num,int key,int low,int high){//辅助方法的实现
if(low > high)
return - low - 1;
int mid = (low + high) / 2;
if(key < num[mid])
return BinarySearch(num,key,low,mid - 1);
else if(key == num[mid])
return mid;
else
return BinarySearch(num,key,mid + 1,high);
}
public static int[] create(int n){
int[] num = new int
;
Scanner input = new Scanner(System.in);
for(int i = 0;i < num.length;i++)
num[i] = (int)(Math.random()*100);
return num;
}

}


递归的辅助方法:重载一个递归实现的方法,第一个方法实现功能的形式,第二个方法增加标志位(辅助参数)来具体实现问题。这是一个很好的递归实现的方法,使得递归更加高效。

示例五:尾递归。以阶乘的实现为例。

通常如果用递归来实现阶乘算法,那么很自然的就能写出如下所示源代码:

public static int jiecheng(int n){
if(n == 0 || n== 1)
return 1;
else
return n * jiecheng(n - 1);
}


上述代码的缺点就是每调用一次jiecheng函数都要创建新的栈内存空间去保存调用点的相关变量,很耗用内存。如果使用尾递归可以节省不少内存。

尾递归:如果从递归调用返回时没有待定的操作(如本例的jiecheng(n - 1))要完成,那么这个递归方法就称为尾递归。

某些编译器可以优化尾递归以减小栈空间。可以使用辅助参数将非尾递归方法转换为递归方法。使用这些参数来控制结果,思路是将待定的操作和辅助参数以一种递归调用不再有待定操作的形式相结合。可能会定义一个带辅助参数的新的辅助递归方法,这个方法可以重载名字相同但签名不同的原始方法。

阶乘的尾递归实现源代码如下所示:

package Blog;

import java.util.Scanner;
import java.io.File;

public class blogTryProject{
//尾递归的形式来实现阶乘
public static void main(String[]args){
System.out.println("Enter the n for factorial :");
Scanner input = new Scanner(System.in);
int n = input.nextInt();
long result = factorial(n,1);
System.out.println(result);
}
public static long facotrial(int n){
return factorial(n,1);
}
public static long factorial(int n,int result){
if(n == 0)
return result;
else
return factorial(n - 1,n * result);
}

}


总结:1)如果一个方法很难直接去实现,那么用递归,并考虑是否可以用辅助参数或尾递归的形式减轻栈内存 的压力;
2)如果能够较容易的使用迭代实现(如菲波那契可以很容易的用循环实现),就不要使用递归!

3)原则上,任何用递归解决的问题,都可以用迭代非递归地解决。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐