写写我理解的递归

递归

阶乘

先用一个例子说明:

function factorial(n){
  var result = n;
  for(var i = n-1;i>0;i--){
    result *= i;
  }
  return result;
}

上面使用循环实现了一个阶乘计算。如何换成递归呢?

如何求3的阶乘?

答:需要求2的阶乘,再乘3

如何求2的阶乘?

答:需要求1的阶乘,再乘2

如何求1的阶乘?

答:需要求1的阶乘,1的阶乘就是1。

这上面的都是重复的,让我们来考虑复杂点的:

如何求n的阶乘?

如果值不是1,你就必须让nn-1的阶乘相乘,可以简写成n! = n(n − 1)! 或者n * f(n-1),f(n-1)就是n-1的阶乘的数学写法。

function factorial(n){
  if(n==1)
    return 1;
  return n*factorial(n-1);
}

当然,这两种方法求阶乘对于我们都能理解,可以理解为局部循环和函数循环,但都达到了一目的。

引自wiki百科(递归):

计算理论可以证明递归的作用可以完全替换循环。

汉诺塔

不太清楚怎么做的,可以先玩下游戏,然后再去实现。

假设有3个盘子,如何把这些盘子从左边柱子上,借助中间柱子移动到最右边?

需要将最上层两个盘子移动到中间柱子,然后将最下面的盘子移动到最右边,再将中间的两个盘子移动到最右边。

如何移动两个盘子?

需要将上面的盘子移动到中间的柱子,将第二个(底下的)盘子移动到目标盘子。

如果只有一个盘子?

直接移动盘子到目标柱子。

function hanoi(disc , src , mid , dst){
    if(disc > 0){
        hanoi(disc-1 , src , dst , mid);
        console.log("Move the "+disc+" from "+src+" to "+dst);
        hanoi(disc-1 , mid , src ,dst);
    }
}

hanoi(3,a,b,c)

//Move the 1 from src to dst
//Move the 2 from src to mid
//Move the 1 from dst to mid
//Move the 3 from src to dst
//Move the 1 from mid to src
//Move the 2 from mid to dst
//Move the 1 from src to dst

分析:

盘子数不能小于1。

  1. 想要移动n个盘子,需要把上面的n-1个盘子移动到辅助柱子上,然后移动最底下的盘子到目标,最后,把辅助柱子上的盘子移动到目标柱子。
  2. 如果递归想不通,那么思考下循环,谁是第一个被移动的盘子(假设最底下是3号盘子)?

    • 想移动3号盘子,需要移动2号盘子,想移动2号,盘子,需要移动1号盘子。所以在递归中,1号盘子先被移动。

    • 1号盘子先被移动到那个柱子上呢?玩游戏我发现,单数盘子想要移动到目标柱子上,需要最顶层的盘子(1号盘子)先移动到目标柱子上;如果是双数盘子,则需要最顶层的盘子先移动到辅助柱子上。所以,到底1号盘子被先移动到那个柱子上,和盘子数有关。

    • 从算法中我们亦可以看到,hanoi(disc-1 , src , dst , mid);交换了目标柱子和辅助柱子,如果disc为偶数,则1号(最顶层的)移动到了中间柱子(mid);如果disc为奇数,则1号盘子移动到了目标柱子(dst)。

  3. 画出移动盘子的树形图

这种访问顺序让我想起了中序遍历,但是和中序遍历有区别,它的每一层节点都是同一个节点。

中序遍历

 //Definition for a binary tree node.
 function TreeNode(val) {
     this.val = val;
     this.left = this.right = null;
 }
//中序遍历
var inorderTraversal = function(root) {
    var result = [];
    var help = function(root){
        if(root===null) return;
        help(root.left);
        result.push(root.val);
        help(root.right);
    }
    help(root);
    return result;
};

//对于这样一个二叉树(nu 是null)

           1
       /       \
      2          3
    /   \      /   \
   4    nu3    5     nu6
  / \         / \   
 nu1 nu2     nu4 nu5 

分析:

help(值为1的节点);
help(值为2的节点)
help(值为4的节点);
help(值为nu1的节点);return;
result.add(4);
help(值为nu2的节点);return;
result.add(2);
help(值为nu3的节点);return;
result.add(1);
help(值为3的节点);
help(值为5的节点);
help(值为nu4的节点);return;
result.add(5);
help(值为nu5的节点);return;
result.add(3);
help(值为nu6的节点);return;
函数运行完毕。

这里,我们通过一个result变量来维持结果集,所有的子问题都返回结果给result变量。

我们想要得到这棵树的中序遍历,我们并没有上来就获取root的值,而是:

  1. 不停地访问他的左节点,直到null为止,然后获取最近的父节点的值。
  2. 然后再按照步骤1访问它的右节点。

这就是递归的力量:要想做整体的事情,只需要知道整体中一部分的解决办法就可以了(要知道何时停止)。

尾递归

再补充最后一个点,刚好还是一个尾递归。

像之前的递归阶乘就是一种尾递归。

摘自wiki:

尾调用的重要性在于它可以不在调用栈上面添加一个新的堆栈帧——而是更新它,如同迭代一般。尾递归因而具有两个特征:
1. 调用自身函数(Self-called);
2.计算仅占用常量栈空间(Stack Space)。

 //Definition for a binary tree node.
 function TreeNode(val) {
     this.val = val;
     this.left = this.right = null;
 }
//二叉树求和
var sumNode = function(root) {
    if(root===null) return 0;
    return root.val + sumNode(root.left) + sumNode(root.right);
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值