Java中递归方法的学习和应用

1.递归算法

        递归在计算机科学中也被称为递归算法,一些程序问题在分解时,会发现这样一种现象:解决该问题的过程是由重复的相同过程组成。类似这样的问题我们都可以通过递归算法进行解决。在计算机语言中,递归算法的实现靠函数的自我调用来完成,我们所能见到的大多数高级编程语言都支持这样的做法。

        递归算法通常有两种方式进行——普通递归和尾递归。递归方法简单的来说就是方法的内部再次调用自身,递归方法会嵌套式的参与运算。这样每一个递归方法都要分配一个函数堆栈进行操作,这就是普通递归,它对内存的消耗是非常大的。

        此外,还有一种递归方式被称为尾递归,尾递归对普通递归进行了优化。如果使用尾递归,需要将递归方式进行特殊的设计,它需要将递归方法在return语句后进行单独调用(即尾调用)。当采用尾递归的时候,会在同一个函数堆栈中进行,效率非常快。作为一名Java程序员,如果你无法将递归方法设计成尾递归的模式也没有任何问题,因为Java并没有对尾递归进行优化,Java对内存的优化是依赖于垃圾收回器。但是如果你是一名C程序员,就需要对尾递归的写法进行掌握了。

        递归算法的优缺点是非常明显,算法实现简单、可读性强是递归算法的优点所在。缺点也同样明显,递归算法会占用大量内存空间,如果递归深度过大,容易发生内存问题。

2.递归的使用

        在设计递归算法的时候,一定要注意两点:1、设计出等价的递归公式。这一点需要我们拥有一些数学基础以及抽象概括能力,等够在复杂的运行过程中,抽象出等价的函数关系。2、递归结束的条件。这一点尤为重要,如果递归方法没有结束条件,就如同死循环一样,让内存和CPU直接"撑爆"。

2.1 斐波拉切数列(Fibonacci sequence)

        在编程语言的学习中,解决一些数学常见问题是学习编程的一种途径。斐波拉切数列就经常出现在编程语言练习中,它是一组有规律数列:“1,1,2,3,5,8,13,21……”,当我们要获取数列中第n位的数字时,可以总结如下公式:

                                                当n=1或者2时,有f(n)=1,当>=3时,有f(n)=f(n-1)+f(n-2)

        当我们要设计一个方法,输出数列的前n个数字的信息,n通过整型参数控制。如果我们需要一个完整的数列,就需要创建一个数列容器,然后将数列中的每一位数字依次计算出来,并保存到容器中,最后按照顺序从容器中输出数列(如下列Java示例):

01.		public static int[] fibs(int n) {
02.			if(n<=0) return new int[0];
03.			int[] fib=new int[n];
04.			
05.			for(int i=0;i<n;i++) {
06.				if(i==0 || i==1) {
07.					fib[i]=1;
08.				}else {
09.					fib[i]=fib[i-1]+fib[i-2];
10.				}
11.			}
12.			return fib;
13.		}

        采用上面的做法好处非常明显,它能够记录每一位数列的值。当我们需要获取整个数列的时候,这样的方式是可取的。在一些时候,我们只想获取其中一位的数值,我们就不需要记录数列,这个时候使用递归的方式就非常方便(如下Java示例所示):

01.		public static int fib(int n) {
02.			if(n<=0) return 0;
03.			if(n==1 || n==2) return 1;
04.			
05.			return fib(n-1)+fib(n-2);
06.		}

        采用上述代码,可以直接获取到数列中第n位的数值,通过观察代码可以发现,使用递归的方式让代码更简洁、阅读起来更方便。我们创建两个测试方法,对两种数列的获取进行测试:

01.		//打印数列
02.		public static void show(int n) {
03.			int[] array=fibs(n);
04.			System.out.println("Fibs (n="+n+")"+Arrays.toString(array));
05.		}
06.		//打印数列中指定位置的数值
07.		public static void showBit(int n) {
08.			System.out.println("Fib "+n+" bit="+fib(n));
09.		}
10.		
11.		public static void main(String[] args) {
12.			Fibonacci.show(1);
13.			Fibonacci.show(2);
14.			Fibonacci.show(3);
15.			Fibonacci.show(10);
16.			Fibonacci.showBit(1);
17.			Fibonacci.showBit(2);
18.			Fibonacci.showBit(3);
19.			Fibonacci.showBit(5);
20.		}

运行结果:
        Fibs (n=1):[1]
        Fibs (n=2):[1, 1]
        Fibs (n=3):[1, 1, 2]
        Fibs (n=10):[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
        Fib 1 bit=1
        Fib 2 bit=1
        Fib 3 bit=2
        Fib 5 bit=5

2.2 汉诺塔(Hanoi)

        汉诺塔是一种有趣的益智游戏,很多人在儿时都玩过这种类似的玩具(如下图所示):
在这里插入图片描述
        汉诺塔的玩法规则是将所有圆盘从A柱上移动到C柱上,并保持从上到下按照大小顺序排放。在移动的过程中,也需要保持从上到下的大小规则。例如上图的三层安诺塔,我们在移动的时候有如下步骤(如下图所示):
在这里插入图片描述
        如果有多个盘子,我们设盘子总数为n,我们可以分为两部分解决,一部分是上面的n-1个盘子,它们
作为一个整体,另一部分是最下面的的盘子n。它们移动可以分为三步:

        1. 将第一部分的n-1个盘子的作为一个整体,从A移动到B柱上,C柱做过度柱。
        2. 接着将第n个盘子从A柱移动到C柱上。
        3. 再将n-1个盘子的整体从B柱移动到C柱上,A柱做过度柱。

        在移动的过程中,需要用剩余的柱子做过度柱。移动过程如下图所示:
在这里插入图片描述
        在用代码实现的时候,我们就可以利用递归的方式进行移动。在下面的代码中,我们为了能够追溯每一步移动时,各个柱子上的盘子情况,我们用一个队列来模拟柱子(实现代码如下所示):

01.	import java.util.HashMap;
02.	import java.util.LinkedList;
03.	import java.util.Map;
04.	
05.	public class Hanoi {
06.		//柱子
07.		private Map<String,LinkedList<Integer>> pillar;
08.		private LinkedList<Integer> a=new LinkedList<>();//a柱
09.		private int step=0;
10.		public Hanoi(int number) {
11.			//初始化A柱
12.			for(int i=1;i<=number;i++) {
13.				a.add(i);
14.			}
15.			
16.			pillar=new HashMap<>();
17.			pillar.put("A", a);
18.			pillar.put("B", new LinkedList<Integer>());
19.			pillar.put("C", new LinkedList<Integer>());
20.		}
21.		
22.		public void play() {
23.			move(a.size(),"A","B","C");
24.		}
25.		//a是移出住,b是过度柱,c是目标柱.
26.		//通过a,b,c三个柱子名称,可以从pillar中获取到对应柱子的盘子队列
27.		public void move(int number,String a,String b,String c) {
28.			if(number==1) {//第一个盘子,直接移动到目标柱上
29.				System.out.print("移动圆盘"+number+"从"+a+"柱到"+c+"柱上");
30.				pillar.get(c).push(pillar.get(a).pop());//移动柱子中的圆盘
31.				addStep();
32.			}else {
33.				move(number-1,a,c,b);//从a移动到b,c做过度step1
34.				System.out.print("移动了圆盘"+number+"从"+a+"柱到了"+c+"柱上");
35.				pillar.get(c).push(pillar.get(a).pop());//移动柱子中的圆盘step2
36.				addStep();
37.				move(number-1,b,a,c);//从b移动到c,a做过度,step3
38.			}
39.		}
40.		
41.		public void addStep() {
42.			this.step++;
43.			System.out.println(",共移动了"+step+"步");
44.			System.out.println("A柱(上到下)"+":"+pillar.get("A"));
45.			System.out.println("B柱(上到下)"+":"+pillar.get("B"));
46.			System.out.println("C柱(上到下)"+":"+pillar.get("C"));
47.		}
48.		
49.		public static void main(String[] args) {
50.			Hanoi han=new Hanoi(3); //测试3个圆盘的移动
51.			han.play();
52.		}
53.	}

示例运行效果:
        移动圆盘1从A柱到C柱上,共移动了1步
        A柱(上到下):[2, 3]
        B柱(上到下):[]
        C柱(上到下):[1]
        移动了圆盘2从A柱到了B柱上,共移动了2步
        A柱(上到下):[3]
        B柱(上到下):[2]
        C柱(上到下):[1]
        移动圆盘1从C柱到B柱上,共移动了3步
        A柱(上到下):[3]
        B柱(上到下):[1, 2]
        C柱(上到下):[]
        移动了圆盘3从A柱到了C柱上,共移动了4步
        A柱(上到下):[]
        B柱(上到下):[1, 2]
        C柱(上到下):[3]
        移动圆盘1从B柱到A柱上,共移动了5步
        A柱(上到下):[1]
        B柱(上到下):[2]
        C柱(上到下):[3]
        移动了圆盘2从B柱到了C柱上,共移动了6步
        A柱(上到下):[1]
        B柱(上到下):[]
        C柱(上到下):[2, 3]
        移动圆盘1从A柱到C柱上,共移动了7步
        A柱(上到下):[]
        B柱(上到下):[]
        C柱(上到下):[1, 2, 3]

3.递归对循环的替代

        在程序开发过程中,很多循环方法都可以使用递归来完成,例如数字的累加和阶层的计算。在下面的代码示例中我们对比这两种算法:

01.	public class Compute {
02.	
03.		//使用循环完成累加
04.		public static int sum(int start,int end) {
05.			int sum=0;
06.			for(int i=start;i<=end;i++) {
07.				sum+=i;
08.			}
09.			
10.			return sum;
11.		}
12.		//使用递归进行累加计算
13.		public static int recSum(int start,int end) {
14.			
15.			if(end<start) return 0;
16.			
17.			return end==start?start:end+recSum(start,end-1);			
18.		}
19.		
20.		public static int factorial(int number) {
21.			if(number<=0) return 0;
22.			int result=1;
23.			for(int i=1;i<=number;i++) {
24.				result*=i;
25.			}
26.			
27.			return result;
28.		}
29.		
30.		public static int recFactorial(int number) {
31.			if(number<=0) return 0;
32.			return number==1?1:number*recFactorial(number-1);	
33.		}
34.		
35.		public static void main(String[] args) {
36.			System.out.println("非递归累加0~100="+Compute.sum(0, 100));
37.			System.out.println("递归累加0~100="+Compute.recSum(0,100));
38.			System.out.println("非递归阶乘6="+Compute.factorial(6));
39.			System.out.println("递归阶乘6="+Compute.recFactorial(6));
40.		}
41.	}

运行效果:
        非递归累加0~100=5050
        递归累加0~100=5050
        非递归阶乘6=720
        递归阶乘6=720

        在上述示例代码中,我们用递归和非递归两种方式解决了累加和循环问题。除此之外,在一些数据结构算法中,递归的使用也非常多,比如二叉树的遍历、排序等,在下面的示例中,我们使用递归的方法进行冒泡排序,并与传统的冒泡排序进行对比:

01.	import java.util.Arrays;
02.	
03.	public class Sort {
04.	
05.		//冒泡排序
06.		public static void bubbleSort(int[] array) {	
07.			for(int i=0;i<array.length;i++) {//外层循环,记录排序的轮次
08.				/** 内存循环用于俩俩比较,从数组第一个元素比较到数组长度减i个轮次.
09.				 * 第一轮从1到length-0,第二轮从1到length-1,依次类推*/
10.				for(int j=1;j<array.length-i;j++) {
11.					//当前一元素大于后一个元素,交换二者的位置。实现大元素后移
12.					if(array[j-1]>array[j]) {
13.						int temp=array[j-1];
14.						array[j-1]=array[j];
15.						array[j]=temp;
16.					}
17.				}
18.			}
19.		}
20.		
21.		//冒泡排序递归
22.		public static void recBubbleSort(int[] array,int index) {	
23.			
24.			for(int i=index+1;i<array.length;i++) { 
25.				if(array[index]>array[i]) {
26.					int temp=array[index];
27.					array[index]=array[i];
28.					array[i]=temp;
29.				}
30.			}
31.			if(index<array.length) recBubbleSort(array, index+1);
32.		}
33.		
34.		public static void main(String[] args) {
35.			int[] array= {1,4,3,-100,323,53,13,554,123,20,43,12};
36.			//Sort.bubbleSort(array);
37.			Sort.recBubbleSort(array,0);
38.			System.out.println(Arrays.toString(array));	
39.		}
40.	}

        传统的冒泡排序需要借助于双层循环进行排序交换,利用递归的方式,可以减少一层循环。在实际的排序中,我们是不推荐使用递归进行排序的,上述示例仅作为递归算法的一种思考。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值