Introduction
递归算法是一种直接或者间接调用自身函数或者方法的算法。递归算法的实质是把问题分解成规模缩小的同类问题的子问题,然后递归调用方法来表示问题的解。递归算法对解决一大类问题很有效,它可以使算法简洁和易于理解。递归算法,其实说白了,就是程序的自身调用。它表现在一段程序中往往会遇到调用自身的那样一种coding策略,这样我们就可以利用大道至简的思想,把一个大的复杂的问题层层转换为一个小的和原问题相似的问题来求解的这样一种策略。递归往往能给我们带来非常简洁非常直观的代码形势,从而使我们的编码大大简化,然而递归的思维确实很我们的常规思维相逆的,我们通常都是从上而下的思维问题, 而递归趋势从下往上的进行思维。这样我们就能看到我们会用很少的语句解决了非常大的问题,所以递归策略的最主要体现就是小的代码量解决了非常复杂的问题。
递归就是方法里调用自身。
在使用递增归策略时,必须有一个明确的递归结束条件,称为递归出口。
递归算法解题通常显得很简洁,但递归算法解题的运行效率较低。所以一般不提倡用递归算法设计程序。
在递归调用的过程当中系统为每一层的返回点、局部量等开辟了栈来存储。递归次数过多容易造成栈溢出等,所以一般不提倡用递归算法设计程序。
递归算法要求。递归算法所体现的“重复”一般有三个要求:
(1) 是每次调用在规模上都有所缩小(通常是减半);
(2) 是相邻两次重复之间有紧密的联系,前一次要为后一次做准备(通常前一次的输出就作为后一次的输入);
(3) 是在问题的规模极小时必须用直接给出解答而不再进行递归调用,因而每次递归调用都是有条件的(以规模未达到直接解答的大小为条件),无条件递归调用将会成为死循环而不能正常结束。
演示
演示 1
1、使用方法嵌套的方式来模拟递归的执行流程
public class RecursionFunction {
void a() {
b();
System.out.println("a");
}
void b() {
c();
System.out.println("b");
}
void c() {
d();
System.out.println("c");
}
void d() {
System.out.println("d");
}
public static void main(String[] args) {
RecursionFunction demo = new RecursionFunction();
demo.a();
}
}
- 注意
1、从上面内容我们可以看到从方法 a -> b -> c -> d 的执行都是先执行的调用链的最深处
2、方法的嵌套是有边界的 d 就是我们的调用边界
3、上面这种调用方式就是我们在遍历树结构时所说的后续遍历
演示二
1、使用方法嵌套的方式来模拟递归的执行流程
public class RecursionFunction {
void a() {
// 执行逻辑
System.out.println("a");
b();
}
void b() {
// 执行逻辑
System.out.println("b");
c();
}
void c() {
// 执行逻辑
System.out.println("c");
d();
}
void d() {
// 执行逻辑
System.out.println("d");
}
public static void main(String[] args) {
RecursionFunction demo = new RecursionFunction();
demo.a();
}
}
- 注意
1、上面这种调用方式就是我们在遍历树结构时所说的后续前序遍历
2、只有一种调用链路不存在中序遍历哈
递归实战
实战 1 (前序遍历)
1、数组的递归遍历 (注意事项:递归需要有边界;需要注意递归函数的返回值)
public class TraverseArray {
private static final int LEN = 10;
private static int[] array = new int[LEN];
static {
for (int i = 0; i < LEN; i++) {
array[i] = i;
}
}
/**
* 前序遍历
* @param index
*/
public static void traverseRecursion(int index) {
// 递归出口
if (index == LEN) {
System.out.println();
return ;
}
// 执行逻辑
System.out.printf("%d " , array[index]);
// 递归调用
traverseRecursion(index + 1);
}
public static void main(String[] args) {
traverseRecursion(0);
}
}
- 注意
1、有没有发现这个例子和我们上面的演示1
的执行流程特别像
2、递归函数的构成通常有 (1、递归边界 2、执行逻辑 3、递归调用)
3、通过调整递归逻辑我们可以得到不同的结果
实战二 (后继遍历)
1、数组的递归遍历 (注意事项:递归需要有边界;需要注意递归函数的返回值)
public class TraverseArray {
private static final int LEN = 10;
private static int[] array = new int[LEN];
static {
for (int i = 0; i < LEN; i++) {
array[i] = i;
}
}
/**
* 前序遍历
* @param index
*/
public static void traverseRecursion(int index) {
// 递归出口
if (index == LEN) {
System.out.println();
return ;
}
// 递归调用
traverseRecursion(index + 1);
// 执行逻辑
System.out.printf("%d " , array[index]);
}
public static void main(String[] args) {
traverseRecursion(0);
}
}
1、通过调整执行逻辑我们就可以达到不一样的执行效果
2、前序遍历是先执行方法逻辑在调用方法,如上演示 1 ,后续遍历是先调用方法在执行方法逻辑,如上演示 2
实战三
1、使用递归计算从 1 - 100 的和
public class ZeroToHundredSum {
public static int answer = 0;
public static final int Hundred = 100;
public static int getSum(int val) {
if (val == Hundred + 1) {
return answer;
}
answer = answer + val;
return getSum(val + 1);
}
public static void main(String[] args) {
getSum(1);
System.out.println(answer);
}
}
\
- 注意
1、使用时需要注意 answer 的作用域,全局作用域和方法及作用域
2、在计算最终结果或者中间结果时需要使用到全局作用域
3、方法及作用域演示代码
public class ZeroToHundredSum2 {
/**
* 使用较小返回进行计算
*/
public static final int Hundred = 2;
public static int getSum(int val) {
int answer = 0;
answer = answer + val;
if (val == Hundred) {
return answer;
}
return getSum(val + 1);
}
public static void main(String[] args) {
int answer = getSum(1);
System.out.println(answer);
}
}
- 注意
1、方法及作用域的值,在最为结果返回时只会计算最后一次的结果,如上图