循环/递归
- 循环:某个代码块重复执行n次,包括while循环和for循环。
for循环通常用于代码块执行次数是确定的情况,所以也被叫做迭代。
while循环的跳出条件往往比较复杂,不单由次数决定
- 递归:函数自己调用自己。
循环和递归的关键都是出口。(循环条件 递归出口)
循环和递归的转化
循环和递归的区别不在于代码的形式(循环是执行一个代码块多次,递归是执行自己多次,做好映射关系,循环形式和递归形式往往可以相互转化。),而在于思维模式不同。
- 循环的思维模式要考虑的更多,循环的条件,循环的操作,条件的更新
- 递归的思维模式则很简单,考虑一个递归出口和一个**递归公式(将问题规模减小的方法)**即可
以一个简单的例子说明循环的思维模式/递归思维模式区别。
同一个问题,循环和递归的思维往往大相径庭,递归思维模式更简单,但时间和空间复杂度都会更高。 下面的例子不是写出某个算法的循环和递归两种解法,而是在同一种思维模式下的循环和递归实现。
斐波那契函数
题目:我们将F(n) = 1,1,2,3,5,8,13…(F(n-1)+F(n-2))这样的数列称为斐波那契数列。
循环思维模式
分析: 将第1、2个数累加得到第三个;将第2、3个树累加得到第四个;将第3、4个累加得到第五个...直到算出第个数。
循环算法的几个关键步骤:
- 初始化
- 确定循环条件
- 执行循环,
(分支)修改条件/跳出循环- 返回结果
- 检查边界情况
实现-循环实现
在这个问题中,对应如下:
- 初始化:加数和被加数a、b分别为1和1,要返回的结果是c
- 循环条件:一共执行n-2次循环
- 执行循环:用a、b求和,得出c;修改循环条件:把b的值赋给a,c的值赋给b
- 返回结果:
- 检查边界情况,n为1或者2
function fibonacci_circle(n) {
if (n === 1 || n === 2) {
return 1
} else {
let a = 1,
b = 1,
c
let count = 0
while (count < n - 2) {
c = a + b
a = b
b = c
count++
}
return c
}
}
let result = fibonacci_circle(6)
console.log(result)
实现-递归实现
- 将循环的代码快转化成函数体,代码块中涉及加数和被加数,所以要对函数的参数做一些修改:函数接收三个参数,第一第二个参数是加数和被加数,第三个参数是要求的数离第一个数的距离。
用a、b求和,得出c;修改递归条件
function fibonacci_recursion(first, second, n) {
// 执行操作
let a = first,
b = second
let c = first + second
// 修改条件
a = second
b = first + second
}
- 确定递归出口:当第三个参数为3(距离只有3)时,返回c
- 考虑边界条件:第三个参数是1或者2的情况
function fibonacci_recursion(first, second, n) {
// 执行操作
let a = first,
b = second
let c = first + second
// 修改条件
a = second
b = first + second
if (n > 3) {
return fibonacci_recursion(a, b, n - 1)
} else if (n === 3) {
return c
} else if (n === 1 || n === 2) {
return 1
}
}
debugger
let result = fibonacci_recursion(1, 1, 6)
console.log(result)
(上面这种直接返回递归调用结果的形式叫尾递归)
递归思维模型
递归算法的关键步骤很简单:
- 确定递归出口
- 确定递归公式
- 检查边界
实现-递归写法
- 递归出口:n为1或者2
- 递归公式,每一个数为前两个数字之和
function fibonacci_recursion(n) {
if (n >= 3) {
// 求和是执行本次操作,n-1和n-2更新了递归条件
let result = fibonacci_recursion(n - 1) + fibonacci_recursion(n - 2)
return result
} else if (n === 1 || n === 2) {
return 1
}
}
debugger
let result = fibonacci_recursion(6)
console.log(result)
(上面这种算法叫非尾递归)
复杂度分析
TODO:之后再写8,还有一点点不太懂
总结
递归由于思维模式简单,很多复杂算法的实现往往都是基于递归而不是单纯的循环。
在写算法的时候,首先去思考,能不能把问题的规模减小,如果能减小,就是提取了一个公式,可以用递归。
然后不论是循环还是递归,都是去确定一个会反复执行的代码块,即一个单步骤要做什么。