快来和叮当学算法吧!
From All CV to No CV!我的梦想是编程不再CV,算法不再死记硬背!
我将算法思想及套路做成了好玩的有趣的视频欢迎你观看偶!
bilibili视频讲解递归
漫话算法[递归&迭代]从盗梦空间到掌握递归思想!
Leetcode五杀系列视频讲解
致力于通过至少5个算法问题!让你发现算法的套路!很快你就能和我一样入门算法并解决算法问题!
Leetcode刷题五杀系列[递归篇]
并且我将常用的算法模板已经上传到了我的github!
知识点路线
通过本文的训练你能解除的疑问
- 什么是递归?什么是尾递归?
- 递归与迭代如何转换?
- 递归和迭代的区别及对比?
- 如何对递归算法优化?
- 什么是自顶向下的设计?什么是自顶向上的设计?
- 什么是递推关系?
- 自顶向下(从未知到已经)通常会出现重复计算如何优化?
盗梦空间
递归就好比盗梦空间而每一层“梦境”(递归)中逻辑都是相同的
- [下潜]:进入下一层递归!
- [回溯]:回到上一层,最终回到第一层就得到了答案!
递归
分类
- 直接递归(自己调自己):
func1
->func1
- 间接递归(间接调自己):
func1
->func2
->func1
- 尾递归:出现在程序的最后一行且值出现一次
组成
- 递归出口:终止条件,解决问题一般要从递归的终止时开始思考!
- 递归体:构造[递归不变式]!
迭代
简介
- 基本算法编程技巧,解决问题的难点在于如何构造[循环不变式]
组成
- 循环出口:终止条件,解决时候要考虑请求while循环条件如[二分查找]的<=和<的情况的含义!
- 循环体:构造[循环不变式]!
算法框架
递归
public void recursion() {
// 1.[终止条件]
if (最后一层) {
// [处理最后逻辑]
return;
}
// 2.[递归体]: 递推关系(不变式)
f(n) = f(.)....
// [下潜]
recursion(进入下一层"梦境");
// 无逻辑即为[尾递归],还有逻辑[非尾递归]
}
迭代
public void iteration() {
// 1.[终止条件]
while (最后一层) {
// 2.[循环体]: 递推关系(不变式)
f(n) = f(.)....
// [变量重置]同递归中的[下潜]
}
}
尾递归
尾递归常常可以与迭代相互转换,但java8并未对尾递归进行优化,因此还是无法避免函数栈溢出的情况!
private static int recursion(int n, int f_2, int f_1) {
// 1.[递归出口]
if (n <= 2) {
return f_1;// f_1是最后的res
}
// 2.[尾递归]
return recursion(n-1, f_1, f_2+f_1);
}
递归的表现形式
直接递归
自己调自己
public int function() {
return function();
}
间接递归
间接调自己
public int function1() {
return function2();
}
public int function2() {
return function1();
}
递推关系
递推属于[迭代算法]中的技巧但个人认为无论是递归还是迭代都可以利用递推的思想解决问题
构造[不变式]求解问题实际上个人的理解就是利用数学归纳法的思想!
- 步骤1:基底(base)即证明简单情况成立!
- 步骤2:归纳(induction)即证明f(k)成立能推出f(k+1)成立!
f ( n ) = n × ( n + 1 ) 2 f(n)=\frac{n \times(n+1)}{2} f(n)=2n×(n+1)
意义:显然使用优秀的的递推公式、数学模型,会得到更高效的算法!
// 对比[普通写法]和用[递推方程]的写法
/**
* 叮当求和
* @param n
*/
public static void dingdangSum(int n) {
int sum = 0;
for (int i = 1; i <= n;i++) {
sum += i;
}
}
/**
* 高斯求和
* @param n
*/
public static void gaussSum(int n) {
int sum = n*(n+1)/2;// O(1)
}
举例:高斯求和
f
(
n
)
=
n
×
(
n
+
1
)
2
f(n)=\frac{n \times(n+1)}{2}
f(n)=2n×(n+1)
-
步骤1:证明基底:f(0)=0,将0带入公式
-
步骤2:归纳
- 假设f(k)成立
- 证明f(k+1)成立
- k+1带入公式中
0 + 1 + 2 + . . . . + k + ( k + 1 ) = ( k + 1 ) × ( ( k + 1 ) + 1 ) 2 0+1+2+....+k+(k+1) = \frac{(k+1) \times((k+1)+1)}{2} 0+1+2+....+k+(k+1)=2(k+1)×((k+1)+1)
-
将左边分为0~k和(k+1),即f(k)+(k+1)
k × ( k + 1 ) 2 + ( k + 1 ) = ( k + 1 ) × ( ( k + 1 ) + 1 ) 2 \frac{k \times(k+1)}{2}+(k+1) = \frac{(k+1) \times((k+1)+1)}{2} 2k×(k+1)+(k+1)=2(k+1)×((k+1)+1) -
继续合并左侧
k × ( k + 1 ) 2 + 2 ( k + 1 ) 2 = ( k + 1 ) × ( ( k + 1 ) + 1 ) 2 \frac{k \times(k+1)}{2}+\frac{2(k+1)}2 = \frac{(k+1) \times((k+1)+1)}{2} 2k×(k+1)+22(k+1)=2(k+1)×((k+1)+1)
-
左侧继续合并
( k + 1 ) ( k + 2 ) 2 = ( k + 1 ) × ( ( k + 1 ) + 1 ) 2 \frac{(k+1)(k+2)}2 = \frac{(k+1) \times((k+1)+1)}{2} 2(k+1)(k+2)=2(k+1)×((k+1)+1) -
右侧合并发现左右相等,这个就是[递推公式]
( k + 1 ) ( k + 2 ) 2 = ( k + 1 ) × ( k + 2 ) 2 \frac{(k+1)(k+2)}2 = \frac{(k+1) \times(k+2)}{2} 2(k+1)(k+2)=2(k+1)×(k+2)
自顶向下
自顶向下即是从未知到已经,从大问题到简单情况!
自底向上
从简单情况base case 到想要求解的大问题!
递归的复杂度计算
时间复杂度:即是递归经过的节点数
空间复杂度:递归有额外的函数栈开销因此
这里以斐波那契的递归树来讲解,递归二叉树的节点数就是递归经过的节点是时间复杂度,递归树的高度及是空间复杂度。
优化的方式
使用迭代
在实际工作中尽量使用迭代,避免使用“递归”时因为少考虑了递归终止条件引发“死递归”导致的cpu性能消耗等问题。
使用记忆化搜索
将递归中已经计算过的问题存储起来,下次计算的时候再取相应的值就可以了!斐波那契数列这道题为例可以将时间复杂度优化至线性阶O(n)
实战训练吧!
理论再多都不如实战,带着这些思想是去解决问题吧!
- [尾递归]总是出现再递归尾部且只有一次
- [尾递归]可以与迭代无缝转换
- [非尾递归]通常可以使用栈来代替[函数栈]转换或者使用[队列]完成
BFS
的迭代 - 递归通常可以使用[记忆化搜索]进行优化,或者使用迭代方式降低函数栈的消耗
- 递归和迭代之间转换的突破点就是找到**[不变式]**
- 二叉树、链表等通常可以使用递归完成!这是因为他们的天然的递归结构决定的,每一层有相似的特性!
- 递归的复杂度计算
空间复杂度:需要额外的空间消耗
时间复杂度:其实就是去计算递归树的节点数目!或则递归经过了多少个链表节点!
Easy-Programming(我的开源项目: 漫话编程)
代码获取
我会持续更新Leetcode刷题的题解以各种各样你想要的!我会同步到线上博客已经微信公众号:CVBear欢迎你的订阅!
代码获取 Github传送门 帮我点个start鼓励我一下呗我!