递归&回溯

171 阅读2分钟

理解递归

一个函数里面调用函数自己本身

小试牛刀

阶乘
function Factorial(n) {
    // 终止条件
    if (n == 1) {
      return 1;
    } else {
      // 调用自己
      return n * Factorial(n - 1);
    }
  }
  /*
    Factorial(2) = 2 * Factorial(1) = 2 * 1;
    Factorial(3) = 3 * Factorial(2) = 3 * 2 * Factorial(1) = 3 * 2 * 1 
    Factorial(4) = 4 * Factorial(3)
*/
  let s = Factorial(20);
  console.log("s", s); // 2432902008176640000
斐波那契数
// 斐波那契  1 1 2 3 5 8 13 21...
  function Fibonacci(n) {
    // 终止条件
    if (n <= 2) {
      return 1;
    } else {
      // 调用自己
      return Fibonacci(n - 1) + Fibonacci(n - 2);
    }
  }
   /*
        Fibonacci(1) = 1;
        Fibonacci(2) = 1 
        Fibonacci(3) = Fibonacci(2) + Fibonacci(1) = 1 + 1 = 2
        Fibonacci(4) = Fibonacci(3) + Fibonacci(2)  = Fibonacci(2) + Fibonacci(1) + Fibonacci(2) = 3
    */
      let res = Fibonacci(50);
      console.log("res", res);  //  12586269025
问题来了
  • 问题,当计算 n = 50时,需要花费很长时间来计算,其中Fibonacci(3),Fibonacci(2)等被调用很多次,占用堆栈内存
  • 递归非常耗费内存资源,因为需要保存很多个调用帧
  • 函数在调用的时,在内存内形成一个调用记录,调用帧,保存调用信息,内部变量等
 function foo1() {
    console.log("1");
  }
  function foo2() {
    foo1();
  }
  function foo3() {
    foo2();
  }
  foo3();
/*
    foo3在调用时,内部会保存调用帧,foo3被调用时,执行foo2函数...也就是说保存了三个调用帧
*/

尾调用优化

 function foo1() {
    console.log("1");
  }
  function foo2() {
    return foo1();
  }
  function foo3() {
    return foo2();
  }
  foo3();
/*
    foo3在执行的时候,调用帧只有一个,return  foo2 ,foo1就不需要保存foo2  foo1了
*/

尾调用

尾巴上调用,某个函数最后一步是调用另外一个函数

尾调用优化

函数的最后一步操作,是不需要保留其他函数的调用帧, 因为调用的位置,内部变量等不会用到,只需要将最后执行另外的函数之前 return

尾递归

尾调用自身,叫尾递归

优化上面的代码

// 阶乘
function Factorial1(n, total) {
   // 终止条件
    if (n == 1) {
      return total;
    } else {
      // 调用自己
      return Factorial1(n - 1, n * total);
    }
}

测试效果

      console.time("Factorial");
      let s = Factorial(20);
      console.log("s", s);
      console.timeEnd("Factorial"); //  0.190185546875 ms
      
      console.time("Factorial1");
      let s1 = Factorial1(20, 1);
      console.log("s1", s1);
      console.timeEnd("Factorial1"); // 0.097900390625 ms
      
      // 快了0.1ms  如下图

23.png

斐波那契
function Fibonacci2(n, total1 = 1, total2 = 1) {
    // 终止条件
    if (n <= 2) {
      return total2;
    } else {
      // 调用自己
      return Fibonacci2(n - 1, total2, total1 + total2);
    }
  }

测试效果

      console.time("Fibonacci");
      let res = Fibonacci(50);
      console.log("res", res);
      console.timeEnd("Fibonacci"); // 96750.11889648438 ms

      console.time("Fibonacci2");
      let res3 = Fibonacci2(50);
      console.log("res3", res3);
      console.timeEnd("Fibonacci2"); // 0.111083984375 ms
      
      // 快了96750 ms  如下图

24.png

经典面试题

 function getValue(obj, arr) {
    if (arr.length == 1) {
      return obj[arr[0]];
    } else {
      return getValue(obj[arr[0]], arr.slice(1));
    }
  }
  let obj = {
    a: {
      b: {
        c: {
          d: "100",
        },
      },
    },
  };

  let arr = ["a", "b", "c", "d"];
  let result = getValue(obj, arr);
  console.log("result", result); // 100

回溯

回溯四部曲:

  • 回溯参数
  • 终止条件
  • 单层递归逻辑
  • 选择其他分支(撤销选择 重置状态)

回溯模版:

result = [];
function backtrack (path, list) {
    if (满足条件) {
        result.push(path);
        return
    }
    
    for () {
        // 单层逻辑
        backtrack (path, list)
        // 撤销选择 重置状态
    }
}