2.递归
- 递归在本质上就是一个栈结构
一个简单的例子,求1+2+3+…+n
-
每个栈帧就代表了被调用中的一个函数, 这些函数栈帧以先进后出的方式排列起来,就形成了一个栈
-
图中栈中每个小方框都是对应的栈帧,每个栈帧都需要记录下当前的n的值, 还要记录下一个函数栈帧的返回值, 然后才能运算出当前栈帧的结果。 也就是说使用多个栈帧是不可避免的。
-
retern
完后,依次弹栈,由先进后出原则弹栈,一次为sum(1)、sum(2)、sum(3)、main方法的顺序出栈
sum(3) = 3+sum(2) //输入参数是3,返回值是3+sum(2),但保存起来,还没有返回输出
sum(2) = 2+sum(1) //输入参数是2,返回值是2+sum(1),但保存起来,还没有返回输出
sum(1) = 1 //输入参数是1,满足递归出口,停止递归,返回1,这是最后一个递归执行元素,却第一个返回值,与栈的 *先进后出* 如出一辙
- 递归方法:但是这种方法当数据太多的时候,调用深度过深,占用大量内存,速度很慢。适用于小规模计算
递归的基本思想
所谓递归,就是有去有回。
递归的基本思想,是把规模较大的一个问题,分解成规模较小的多个子问题去解决,而每一个子问题又可以继续拆分成多个更小的子问题。
最重要的一点就是假设子问题已经解决了,现在要基于已经解决的子问题来解决当前问题;或者说,必须先解决子问题,再基于子问题来解决当前问题。
或者可以这么理解:递归解决的是有依赖顺序关系的多个问题。
我们假设一个抽象问题有两个时间点要素:开始处理,结束处理。
那么递归处理的顺序就是,先开始处理的问题,最后才能结束处理。
假设如下问题的依赖关系:【A】—依赖—>【B】—依赖—>【C】
我们的终极目的是要解决问题A,那么三个问题的处理顺序如下:开始处理问题A;由于A依赖B,因此开始处理问题B;由于B依赖C,开始处理问题C;结束处理问题C;结束处理问题B;结束处理问题A。
解决问题的顺序就是:C–>B–>A
再举个典型例子: 从尾到头反转链表
import java.util.ArrayList;
import java.util.Stack;
public class Title3 {
public static ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> list = new ArrayList<>();
if (listNode == null){
return list;
}
return solve(list,listNode);//因为solve返回的是ArrayList,而printListFromTailToHead也需要返回list,所以return
}
public static ArrayList<Integer> solve(ArrayList<Integer> list, ListNode listNode) {
//自定义方法来进行递归,先进后出,把细节处理完,也就是把链表反转在返回到printListFromTailToHead大局方法中,以便
//直接调用printListFromTailToHead即可解决问题
if(listNode.next != null){//递归执行条件,当不为最后一个节点时
list = solve(list, listNode.next);//一直递归调用,开辟栈内存(栈帧)来存放当前是第几个节点
}
list.add(listNode.val);//上箭头,给上面,
// System.out.println(list);[9]
return list;//这里与栈的先进后出一致,0-9一次进来,9-0依次返回
}
}
//
[9]
[9, 8]
[9, 8, 7]
[9, 8, 7, 6]
[9, 8, 7, 6, 5]
[9, 8, 7, 6, 5, 4]
[9, 8, 7, 6, 5, 4, 3]
[9, 8, 7, 6, 5, 4, 3, 2]
[9, 8, 7, 6, 5, 4, 3, 2, 1]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
//先进后出
}