BUAA士谔书院T大班2021年10月30日程设串讲--递归与递推

2021年10月30日程设串讲

递推与递归——by Toby

首先,让我们来讲一个故事吧!

在一个晴朗的早晨,纱世里呆在她的房间里,而你站在她的房间外面。(想到了什么?!)(雾)

有意思的是,纱世里突然发现她的房间里还有一扇门,于是她怀着好奇,推门而入。
更加令人惊奇的是,她进入的新房间和原来的房间一模一样,而且也有那样神奇的门,于是她又推门而入。
可是,眼前的房间还是一模一样。纱世里感到十分迷惑,但她依然打算再走下去。
不过她很聪明,在这个房间里种下了一朵花,希望自己回来的时候花已然盛开。然后她进入了下一个房间。
随后,她又看到了许许多多同样的房间,并且她在途经的每一个房间中都种下了一颗种子。

直到……

此时此刻,纱世里不知道的是,她处在你所编写的程序之中,而你将决定她的命运。
请选择下面的几种代码决定故事的走向:

  • A.

    int main()
    {
        for (int i = 0; i < total_rooms; i++)
        {
            create_room();
        }
        return 0;
    }
    
  • B.

    void recursion(int room_id)
    {
        if(room_id <= 0) return;
        create_room();
        recursion(room_id + 1);
    }
    
    int main()
    {
        recursion(1);
        return 0;
    }
    
  • C.

    void recursion(int room_id)
    {
        if(room_id > total_room) return;
        create_room();
        recursion(room_id + 1);
    }
    
    int main()
    {
        recursion(1);
        return 0;
    }
    

一般递归的形式

上面这个故事的C选项已经呈现了递归的一种写法。不过显然的是,这种写法也可以用A中的循环代替。实际上,几乎所有的递归都可以用循环代替,不过,有时候写起来就会十分麻烦。

下面我们先来完整的实现一下上面的故事:

void SayoriRoom(int room_id)
{
    if(room_id == total_room)
    {
        leaveRoom(room_id);
    }
    enterRoom(room_id);
    raiseFlower();
    SayoriRoom(room_id+1);
    pluckFlower();
    exitRoom(room_id);
}

如你所见,这是一个很基础递归模式:
即,在递归调用自己前做一些事,然后递归调用自己,回来后用做一些事,然后结束。


答疑

汉诺塔

很多人问这个问题啊。所以要重点说一说。

首先找递归边界! 任何递归,首先都要有递归边界。

  • 递归边界,首先肯定是很简单的问题才作为递归边界,比如,只有 1 1 1个圆盘的时候。
  • 显然,只需要移动 1 1 1次即可。

然后再想递归式! 如果递归式特别难想,不妨先想想只有 2 2 2个圆盘,只有 3 3 3个圆盘这样的情况。

  • 只有 2 2 2个圆盘时结论也比较好想,即把小圆盘先放在中间的柱子上,把大圆盘移动到目标柱,再把小圆盘移动到目标柱。因此是有 3 3 3次操作。
  • 至于 3 3 3个圆盘,可以仿照 2 2 2的做法,先把小圆盘和中圆盘都移到中间的柱子上(这根据2需要3步),再把大圆盘移动到目标柱,最后把小圆盘和中圆盘移动到目标柱。于是需要 3 + 1 + 3 = 7 3+1+3=7 3+1+3=7步。
  • 最后呢,我们发现。对于有 n n n个圆盘的情况,可以先把前 n − 1 n-1 n1个圆盘移动到中间的柱子上,再把最大的圆盘移动到目标柱,最后把前 n − 1 n-1 n1个圆盘移动到目标柱。于是需要的移动次数 f ( n ) = f ( n − 1 ) + 1 + f ( n − 1 ) = 2 f ( n − 1 ) + 1 f(n)=f(n-1)+1+f(n-1)=2f(n-1)+1 f(n)=f(n1)+1+f(n1)=2f(n1)+1

于是此题解决:

套用刚刚故事中的递归的模板即可:

int Hanoi(int n) //如有必要,请使用long long作为返回值
{
    if(n <= 0) return 0; //如果我们没有圆盘……
    if(n == 1) return 1; //递归边界一定要有,切记切记!(不然你会喜提“晴天娃娃”)
    int ans;//这次进入下一个房间前我们不用种花,而是需要一个容器装花(声明一个存放答案的变量)。
    ans = 2 * Hanoi(n-1) + 1;//进入下一个房间(递归的调用自己),这次我们用的是递推公式。
    return ans;//记得把花带回去哦!(把计算结果传递回去)
    /*当然用心的同学可以看出,上面三行实际上可以直线一行:
    return 2 * Hanoi(n-1) + 1;
    实际上这一行代码就实现了好几个功能*/
}
E5-F 世界线

这题这出题人是真差劲,题意表达非常不明确,还好有样例解释

简单来说,这题就是根据op的值确定下次穿越到哪个世界线。

直接上代码吧:

int WorldLine(int depth, char world) //depth表示当前层数,world表示当前位置
{
    if(depth == N) //依题意,N为递归边界
    {
        if(world == 'a') return a;
        if(world == 'b') return b;
        if(world == 'c') return c;
    }
    int ans = 0; //声明并初始化储存容器
    //下面依次考虑我是否要进入某条世界线
    if(op[depth] & 4) ans += WorldLine(depth+1, 'a');
    if(op[depth] & 2) ans += WorldLine(depth+1, 'b');
    if(op[depth] & 1) ans += WorldLine(depth+1, 'c');
    return ans; //结算答案
}
// 这个代码其实有个小问题,不知道大家发现没有(和递归并没有关系的问题)
组合数问题

接下来我们来算一算组合数,首先大家要熟知下面这两个公式:
C m n = C m − 1 n + C m − 1 n − 1 C m n = C m m − n C^n_m = C^n_{m-1} + C^{n-1}_{m-1} \\ C^n_m = C^{m-n}_m Cmn=Cm1n+Cm1n1Cmn=Cmmn
这个是高中数学内容吧

既然有了递推式,我们就可以写代码了呀!

int Combination(int m, int n) //m里面选n个
{
    if(n == 0 || n == m) return 1; //m里面选0个,m里面选n个,都得1
    if(n > m / 2) return Combination(m, m - n); //这一步是为了加快速度
    return Combination(m-1, n) + Combination(m-1, n-1);
}

一起来做题

其实说得多不如做得多,下面我们一起来做个题吧!

P1010幂次方
P1464记忆化
P1928解压缩
P1028简易DP

这是我选的一些道,难度比较正常,理论上你们很有可能会考此类题目。
(不过我估计考试题将比这个简单一些,所以你只要写出这些题,类似的递归问题就都能解决了)

如果还不满足的话,送你整整一个题单:
参考题单


最后的最后

最后就要祝大家明天AK快乐!

这堂课录像了呢!可能会发B站呢!据说一键三连的小伙伴明天会更容易AK哦!


故事的答案

1-A

纱世里走啊走啊,终于,她走到了最后一个房间,这个房间的房门通往外界,纱世里非常高兴,冲了出去,进入了另一个世界。遗憾的是,她再也不可能回来了,不可能看到她种下的花朵已然盛开,不可能看到站在房门口等着她的你。

回到题目


1-B

纱世里走啊走啊,走了很远很远,但是从未看到尽头,也找不到回来的路了。终于,站在门口的你,忍不住推门而入,顺着纱世里走过路,看见她种下的花朵已经盛开,心情很好。可是当你在漫长的旅途之后,等待你的,不是纱世里,而是晴天娃娃。

回到题目


1-C

纱世里走啊走啊,终于,她来到了最后一个房间。这个房间与前面的房间都有所不同,本该通往下一个房间的大门死死封闭,上面的告示写着应该如何原路返回。纱世里依照指示,回到了上一个房间,迎接她的是扑面而来的花香,她种下的花,盛开了。她摘下其中的一支,又返回了上一个房间。所有她曾经过的房间里的花朵都已经盛开,她从每个房间里选取了一朵最美最香的,捧在手中。最终,她回到了最初了房间,她将沿途收集到的花朵包装好,怀着愉快的心情走出房门,将这一大捧花束全都送给了你。

回到题目

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值