目录
一、递归是什么
1.递归的原理
我们先来看一个小故事:
从前有座山,山上有座庙,庙里有个老和尚在给小和尚讲故事,讲的什么故事呢?讲的是:从前有座山,山上有座庙,庙里有个老和尚在给小和尚讲故事,讲的什么故事呢?讲的是:从前有座山,山上有座庙,庙里有个老和尚在给小和尚讲故事,讲的什么故事呢?……
相信大家都知道这个故事,它的特征就是在自身中又包含了自身,这其实就是递归的思想。如果在一个方法内部,又调用了该方法本身,那么就叫做递归(Recursion)。递归在解决复杂问题时非常有用,它可以将大问题分解为更小的子问题,这些子问题与原问题类似但却有着更小的规模,因此可以简化问题,实现更高效的算法。
简言之,递归就是方法或者函数调用自身,递归的形式有以下两种:
- 直接递归:方法自己调用自己
- 间接递归:方法调用其他方法,其他方法又回调方法自己
不过,使用递归时有两个必要条件需要满足:
- 分解得到的子问题必须与原问题解法相同
- 必须有一个明确的结束条件(即递归出口),以防止无限循环
2.递归与for循环的联系
根据递归的原理,可以发现一维的for循环,都可以用递归实现,只需要每次调用递归函数时改变参数(要求当前参数与上一次调用时使用的参数存在逻辑关联)即可。
下面我们分别用for循环和递归实现1~100的累加,代码如下:
- for循环
int tempResult = 0;
for(int i = 1; i <= 100; i++) {
tempResult += i;
} // of for i
System.out.println("Result by for is " + tempResult);
- 递归
public static int Recursion(int n) {
if(n == 1) {
return 1;
} // of if
return Recursion(n - 1) + n;
} // of Recursion
public static void main(String[] args) {
System.out.println("Rssult by recursion is " + Recursion(100));
} // of main
可以看到,这两种方法得到的运行结果是完全一样的。在使用递归时,我们加入了一个if语句(当输入的参数n = 1时,直接返回1,不再继续进行调用),这其实就是我们之前提到的明确的结束条件,也就是递归的出口。
其实,对于类似累加的递推操作,都可以用如上的递归函数作为模板。
3.递归的应用场景
递归在很多问题中都有应用,特别是那些可以被分解为更小规模的子问题的情况,常见的递归应用场景如下:
- 数学问题,比如累加、阶乘、斐波那契数列等
- 数据结构操作,比如遍历树的节点、链表反转等
- 搜索和回溯算法,比如深度优先搜索、回溯法等
- 分治法,比如归并排序、快速排序等
二、代码实现
1.0~N的累加实现
在上面,我们以1~100的累加为例,学习了对于累加这种递推形式应该怎样构造递归函数,这里直接套用即可,代码如下:
/**
*********************
* Sum to N. No loop, however a stack is used.
*
* @param paraN The given value.
* @return The sum.
*********************
*/
public static int sumToN(int paraN) {
if(paraN <= 0) {
// Basis.
return 0;
} // of if
return sumToN(paraN - 1) + paraN;
} // of sumToN
显然,这里使用到的递归方程为 f(N) = f(N - 1) + N,而递归出口用if语句控制,当 paraN <= 0 时,直接返回0,不再继续进行函数调用,递归也就结束了。
2.斐波那契数列实现
我们先来了解一下斐波那契数列的基本概念:
斐波那契数列(Fibonacci sequence)又称黄金分割数列或兔子数列,其数值为:1,1,2,3,5,8,13,21,34,…即从该数列的第三项开始,每一项都等于前两项之和。其递推方式可以概括为F(0) = 0,F(1) = 1,F(n) = F(n-1) + F(n-2)(n≥2,n∈N*)。
接下来,就可以进行代码模拟了。根据定义,很容易就可以得到递推方程为 f(N) = f(N - 1) + N,然后,需要注意这里应该有两个递归出口,一个是 paraN <= 0 时,直接返回0,另一个是 paraN = 1时,直接返回1。因此,创建方法如下:
/**
*********************
* Fibonacci sequence.
*
* @param paraN The given value.
* @return The sum.
*********************
*/
public static int fibonacci(int paraN) {
if(paraN <= 0) {
// Negative values are invalid. Index 0 corresponds to the first element 0.
return 0;
} // of if
if(paraN == 1) {
// Basis.
return 1;
} // of if
return fibonacci(paraN - 1) + fibonacci(paraN - 2);
} // of fibonacci
3.数据测试
接着,我们照例进行一些数据测试,分别是0~5的累加、0~ -1的累加以及斐波那契数列前9项的计算,代码如下:
/**
*********************
* The entrance of the program.
*
* @param args Not used now.
*********************
*/
public static void main(String[] args) {
int tempValue = 5;
System.out.println("0 sum to " + tempValue + " = " + sumToN(tempValue));
tempValue = -1;
System.out.println("0 sum to " + tempValue + " = " + sumToN(tempValue));
for(int i = 0; i < 10; i++) {
System.out.println("fibonacci " + i + " : " + fibonacci(i));
} // of for i
} // of main
4.完整的程序代码
package datastructure;
/**
* Recursion. A method can (directly or indirectly) invoke itself. The system
* automatically creates a stack for it.
*
*@auther Xin Lin 3101540094@qq.com.
*/
public class Recursion {
/**
*********************
* Sum to N. No loop, however a stack is used.
*
* @param paraN The given value.
* @return The sum.
*********************
*/
public static int sumToN(int paraN) {
if(paraN <= 0) {
// Basis.
return 0;
} // of if
return sumToN(paraN - 1) + paraN;
} // of sumToN
/**
*********************
* Fibonacci sequence.
*
* @param paraN The given value.
* @return The sum.
*********************
*/
public static int fibonacci(int paraN) {
if(paraN <= 0) {
// Negative values are invalid. Index 0 corresponds to the first element 0.
return 0;
} // of if
if(paraN == 1) {
// Basis.
return 1;
} // of if
return fibonacci(paraN - 1) + fibonacci(paraN - 2);
} // of fibonacci
/**
*********************
* The entrance of the program.
*
* @param args Not used now.
*********************
*/
public static void main(String[] args) {
int tempValue = 5;
System.out.println("0 sum to " + tempValue + " = " + sumToN(tempValue));
tempValue = -1;
System.out.println("0 sum to " + tempValue + " = " + sumToN(tempValue));
for(int i = 0; i < 10; i++) {
System.out.println("fibonacci " + i + " : " + fibonacci(i));
} // of for i
} // of main
} // of class Recursion
运行结果
总结
递归是一种强大且常用的编程技巧,在java中经常被使用,它可以将复杂的问题分解为更小规模的子问题,简化代码逻辑;同时能够直观地表达问题的解决思路,提高代码的可读性;而且在某些算法中还能实现更高效的解决方法。不过,递归也存在一些不足,因为递归的函数调用本质上是栈的使用过程,所以如果递归中没有明确的结束条件,那么就非常容易导致用栈过深,造成栈溢出错误,同时递归调用需要创建多个栈帧,对系统资源有一定的消耗。
最后,递归解决问题的思想非常值得掌握,它并不按照先后顺序从头开始,而是忽略个别细节,总结出一般的递推关系,然后利用这个递推关系将分解成的子问题一一连接起来。