算法:从斐波那契数列看循环和递归

1,290 阅读4分钟

循环/递归

  • 循环:某个代码块重复执行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个累加得到第五个...直到算出第个数。

循环算法的几个关键步骤:

  1. 初始化
  2. 确定循环条件
  3. 执行循环,
    (分支)修改条件/跳出循环
  4. 返回结果
  5. 检查边界情况

实现-循环实现

在这个问题中,对应如下:

  1. 初始化:加数和被加数a、b分别为1和1,要返回的结果是c
  2. 循环条件:一共执行n-2次循环
  3. 执行循环:用a、b求和,得出c;修改循环条件:把b的值赋给a,c的值赋给b
  4. 返回结果:
  5. 检查边界情况,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)

实现-递归实现

  1. 将循环的代码快转化成函数体,代码块中涉及加数和被加数,所以要对函数的参数做一些修改:函数接收三个参数,第一第二个参数是加数和被加数,第三个参数是要求的数离第一个数的距离。
    用a、b求和,得出c;修改递归条件
function fibonacci_recursion(first, second, n) {
  // 执行操作
  let a = first,
    b = second
  let c = first + second
  // 修改条件
  a = second
  b = first + second
}
  1. 确定递归出口:当第三个参数为3(距离只有3)时,返回c
  2. 考虑边界条件:第三个参数是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)


(上面这种直接返回递归调用结果的形式叫尾递归)

递归思维模型

递归算法的关键步骤很简单:

  1. 确定递归出口
  2. 确定递归公式
  3. 检查边界

实现-递归写法

  1. 递归出口:n为1或者2
  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,还有一点点不太懂

总结

递归由于思维模式简单,很多复杂算法的实现往往都是基于递归而不是单纯的循环。

在写算法的时候,首先去思考,能不能把问题的规模减小,如果能减小,就是提取了一个公式,可以用递归。

然后不论是循环还是递归,都是去确定一个会反复执行的代码块,即一个单步骤要做什么。