2021-09-13 10:42:01 +08:00
|
|
|
|
# 递归
|
2017-04-01 17:50:44 +08:00
|
|
|
|
|
2021-11-10 09:53:44 +08:00
|
|
|
|
递归是一种设计和描述算法的有力工具。 也是回溯法和动态规划的基础。
|
|
|
|
|
|
|
|
|
|
|
|
递归算法执行过程分 `递推` 和 `回归` 两个阶段
|
2017-04-01 17:50:44 +08:00
|
|
|
|
|
|
|
|
|
|
在 `递推` 阶段,将大的问题分解成小的问题
|
|
|
|
|
|
在 `回归` 阶段,获得最简单问题的解后,逐级返回,依次得到稍微复杂情况的解,知道获得最终的结果
|
|
|
|
|
|
|
2021-11-07 00:16:03 +08:00
|
|
|
|
1) 确定递归公式 , 比如 斐波那契数列 问题中的 `fib(n)=fib(n-1)+fib(n-2)`
|
2021-11-10 14:27:03 +08:00
|
|
|
|
2) 确定边界条件 bad case
|
2017-04-01 17:50:44 +08:00
|
|
|
|
|
2021-09-13 11:36:49 +08:00
|
|
|
|
> 自顶向下的递归,自底向上是迭代
|
2017-04-01 17:50:44 +08:00
|
|
|
|
|
2021-09-29 19:20:38 +08:00
|
|
|
|
|
2021-10-11 10:35:23 +08:00
|
|
|
|
递归运行效率较低,因为有函数调用的开销,递归多次也可能造成栈溢出。
|
|
|
|
|
|
|
2021-11-10 09:57:16 +08:00
|
|
|
|
### 递归公式
|
|
|
|
|
|
|
|
|
|
|
|
快排
|
|
|
|
|
|
|
|
|
|
|
|
归并排
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
二叉树
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-11-10 09:55:22 +08:00
|
|
|
|
|
|
|
|
|
|
### 递归树
|
|
|
|
|
|
|
2021-11-10 09:53:44 +08:00
|
|
|
|
画递归树,可以很方便地看到是否存在重叠子问题,如果有的话,就可以采用动态规划。
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-10-11 10:35:23 +08:00
|
|
|
|
|
2021-10-25 16:39:57 +08:00
|
|
|
|
### 其它案例
|
|
|
|
|
|
|
|
|
|
|
|
* 阶乘计算
|
|
|
|
|
|
* 梵塔问题 (三根针1,2,3表示,1号从小到大n个盘子,先要都移到3号上,不能出现大盘压小盘,找出移动次数最少的方案)
|
|
|
|
|
|
* 快速排序
|
2021-11-05 10:34:02 +08:00
|
|
|
|
* 很多树算法都是递归思想实现
|
2021-10-25 16:39:57 +08:00
|
|
|
|
|
|
|
|
|
|
|
2021-10-11 10:35:23 +08:00
|
|
|
|
|
2021-09-13 10:42:01 +08:00
|
|
|
|
## 斐波那契数列
|
2017-04-01 17:50:44 +08:00
|
|
|
|
|
|
|
|
|
|
fib(n)=fib(n-1)+fib(n-2)
|
|
|
|
|
|
|
2021-09-13 10:42:01 +08:00
|
|
|
|
#### 递归实现
|
2017-04-01 17:50:44 +08:00
|
|
|
|
|
|
|
|
|
|
```
|
2021-09-13 10:42:01 +08:00
|
|
|
|
int fib(int N) {
|
|
|
|
|
|
if (N == 1 || N == 2) return 1;
|
|
|
|
|
|
return fib(N - 1) + fib(N - 2);
|
|
|
|
|
|
}
|
2017-04-01 17:50:44 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2021-09-13 10:42:01 +08:00
|
|
|
|
这个递归的算法效率非常差,存在大量重复计算。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### 非递归实现
|
|
|
|
|
|
|
|
|
|
|
|
我们可以有一个【缓存】,每次遇到一个子问题先去「缓存」里查一查,如果发现之前已经解决过这个问题了,直接把答案拿出来用,不要再耗时去计算了。
|
|
|
|
|
|
|
2017-04-01 17:50:44 +08:00
|
|
|
|
```
|
2021-09-13 10:42:01 +08:00
|
|
|
|
public int fib(int n){
|
|
|
|
|
|
if (n == 0) return 0;
|
|
|
|
|
|
|
|
|
|
|
|
//缓存
|
|
|
|
|
|
int[] dp = new int[n + 1];
|
|
|
|
|
|
|
|
|
|
|
|
// base case
|
|
|
|
|
|
dp[0] = 0; dp[1] = 1;
|
|
|
|
|
|
|
|
|
|
|
|
// 状态转移
|
|
|
|
|
|
for (int i = 2; i <= n; i++) {
|
|
|
|
|
|
dp[i] = dp[i - 1] + dp[i - 2];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return dp[n];
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
说一个细节优化点,当前状态只和之前的两个状态有关,其实并不需要那么长的一个 DP table 来存储所有的状态,只要想办法存储之前的两个状态就行了。所以,可以进一步优化,把空间复杂度降为 O(1):
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
int fib(int n) {
|
|
|
|
|
|
if (n < 1) return 0;
|
|
|
|
|
|
if (n == 2 || n == 1)
|
|
|
|
|
|
return 1;
|
|
|
|
|
|
int prev = 1, curr = 1;
|
|
|
|
|
|
for (int i = 3; i <= n; i++) {
|
|
|
|
|
|
int sum = prev + curr;
|
|
|
|
|
|
prev = curr;
|
|
|
|
|
|
curr = sum;
|
|
|
|
|
|
}
|
|
|
|
|
|
return curr;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
2021-09-29 19:20:38 +08:00
|
|
|
|
## 合并有序链表
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
递归解法
|
2021-10-25 16:39:57 +08:00
|
|
|
|
```
|
2021-09-29 19:20:38 +08:00
|
|
|
|
public static LinkedNode mergeSeqLink2(LinkedNode l1, LinkedNode l2){
|
|
|
|
|
|
|
|
|
|
|
|
if(l1 == null){
|
|
|
|
|
|
return l2;
|
|
|
|
|
|
}
|
|
|
|
|
|
if(l2 == null){
|
|
|
|
|
|
return l1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if(l1.value < l2.value){
|
|
|
|
|
|
l1.next = mergeSeqLink2(l1.next,l2);
|
|
|
|
|
|
return l1;
|
|
|
|
|
|
}else{
|
|
|
|
|
|
l2.next = mergeSeqLink2(l2.next,l1);
|
|
|
|
|
|
return l2;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
非递归解法
|
2021-10-25 16:39:57 +08:00
|
|
|
|
|
2021-09-29 19:20:38 +08:00
|
|
|
|
```
|
|
|
|
|
|
public static LinkedNode mergeSeqLink(LinkedNode l1, LinkedNode l2){
|
|
|
|
|
|
if (l1 == null) return l2;
|
|
|
|
|
|
if (l2 == null) return l1;
|
|
|
|
|
|
|
|
|
|
|
|
LinkedNode result = new LinkedNode(0);
|
|
|
|
|
|
LinkedNode tmp = result;
|
|
|
|
|
|
|
|
|
|
|
|
while (l1 != null && l2 != null) {
|
|
|
|
|
|
if (l1.value < l2.value) {
|
|
|
|
|
|
tmp.next = l1;
|
|
|
|
|
|
tmp = tmp.next; //tmp 指针前进
|
|
|
|
|
|
l1 = l1.next ; //l1 前进
|
|
|
|
|
|
} else {
|
|
|
|
|
|
tmp.next = l2;
|
|
|
|
|
|
l2 = l2.next;
|
|
|
|
|
|
tmp = tmp.next;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (l1 != null) {
|
|
|
|
|
|
tmp.next = l1;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (l2 != null) {
|
|
|
|
|
|
tmp.next = l2;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result.next;
|
|
|
|
|
|
}
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2021-09-13 10:42:01 +08:00
|
|
|
|
|
|
|
|
|
|
|
2017-04-01 17:50:44 +08:00
|
|
|
|
|
|
|
|
|
|
|
2021-10-11 10:35:23 +08:00
|
|
|
|
|
|
|
|
|
|
|
2017-04-01 17:50:44 +08:00
|
|
|
|
|