Easy Algorithms系列——详解递归与分治

本文是Easy Algorithms系列的第一篇,介绍了递归与分治的概念及其关系。递归是函数自我调用直到满足返回条件的过程,而分治是将大问题分解为独立小问题求解的策略。两者虽常一起讨论,但本质不同。文中通过实例阐述了递归的使用,如阶乘和斐波那契数列,并提到了分治的应用,如归并排序和汉诺塔问题。
摘要由CSDN通过智能技术生成

在这里插入图片描述

写在前面: 嗨,大家好。欢迎各位有缘人来到我的博客,这里是Easy Algorithms (EA) 系列第一弹。之所以写这个专栏,完全是为了回顾和整理自己的算法知识。本科四年,我大概搞了三年半的算法竞赛,通过自己的不懈努力,还算取得了一丢丢丢丢小小的成就。实话实说,我是从大一下学期才接触算法竞赛的,再加上自己的智力远不如那些OI大佬,所以自认为自己也就是一个中下水平,也只是将将够应付基础的算法面试罢了。后来,有幸保送到中国科学院大学读研究生,慢慢发现之前的竞赛经历无形之中给我很多帮助和启发,并且它锻炼了我有较为不错的coding能力和工程能力,学什么新东西速度也都还算可以,思维也得到了很好的锻炼。最近,偶尔发现AI中的很多场景其实都还是需要传统的算法的,比如物体跟踪领域,就常常需要图论的知识,比如二分图,匈牙利算法等。于是,决定开个专栏回顾下这下知识,希望对自己未来的科研之路有所帮助。此外,我深知在一个弱校一个人钻研算法的头痛之处,所以也希望可以用简单的语言帮助那些需要帮助的同学们。所有的文章,我尽量用简单易懂的原创作品呈现给大家,当然如果发现有高手比我写的好,我也会加以转载并注明出处,侵删,谢谢!
由于本人水平有限,博文中如果有什么错误的地方,恳请大家可以批评指正,不胜感激!
最后,用一句话送给那些犹豫不决想要放弃的有缘人,“锲而舍之,朽木不折。锲而不舍,金石可镂”!

递归与分治

今天我们就来一起学习一下递归和分治算法。

递归与分治关系

提起递归与分治,很多人总会冒出一个疑问,递归与分治到底是什么关系!??? 包括我自己在内,很长时间对于二者区分不开。那么今天,我便告诉大家,递归和分治半毛钱关系都没有,他们根本就没有任何联系! 为什么还有很多人把他们放在一起讲呢?只不过分治法可以通过递归高效的来解决,其实你也完全可以用循环暴力枚举的方法来实现,只是比较麻烦罢了。

递归

一句话概括递归就是: 某个函数重复的自己调用自己直到满足某种返回条件是停止调用,并逐层返回。 这个返回条件一般就是我们从实际问题中发现并定义的。通常情况下,通过递归我们把原问题分解成了更小的子问题,直到最后被解决。

int func(传入数值) {
  if (终止条件) return 最小子问题解;
  return func(缩小规模);
}

递归和枚举的区别在于:枚举是横向地把问题划分,然后依次求解子问题,而递归是把问题逐级分解,是纵向的拆分。

递归本质上是一种编程技巧,或者说一种解决问题的思维方式,其更像是我们常说的数学归纳法的思想。

在数学归纳法中,有时候我们很难证明一个递推公式的有效性。于是,我们便从一个小的数开始,比如 n = 1 n=1 n=1,然后证明该公式对第 k k k个数成立,那么此时在证明对第 k + 1 k+1 k+1个数也成立,进而就可以证明我们的公式正确。详细过程见百度百科

递归和数学归纳法的过程类似,但是其是一个相反的过程。递归存在所谓的结束条件,我们从一个大规模开始,逐渐缩小问题的规模,子问题得到解决,我们的大问题自然就得到了解决。而数学归纳法,这个数值是无穷无尽的,不存在所谓的结束条件,我们通过k和k+1将结论延伸到无穷,就证明了正确性。

递归的过程

很多人搞不明白递归的过程,甚至一搞到递归的过程就蒙蔽。我的建议是,不要试图想明白每一步递归到底做了什么子问题是如何解决的,而是理解函数的作用,并确信最后问题能被解决即可。
实际上,递归的过程可以看成是一个压栈和出栈的过程(这里假装大家都了解基本的数据结构队列和栈,栈就是 “先进后出” 的结构罢了),所以如果没有返回条件,当内存耗尽递归也就爆栈了。下面是一个简单的递归压栈出栈的过程图片来源,感谢
调用Age(5)
在这里插入图片描述

在这里插入图片描述
相信看了上面这个图,我们已经可以很好的理解递归的整个过程了。那么我们为什么要写递归呢?

为什么要写递归

  • 首先,递归的可解释强,它程序的结构更清晰、简洁、更容易让人理解。
  • 便于求解,而不需要探究子问题到底怎么被解决
  • 再就是锻炼思维能力,因为他是一个从后往前看的过程
  • 有时候高效,有时候低效(因为需要消耗内存,当数据量大不建议)

递归的例子

  1. 递归的一个经典的例子就是求n的阶乘。很显然,求n的阶乘f(n)等价于求n-1的阶乘f(n-1)在乘以n,即 f ( n ) = n ∗ ( n − 1 ) f(n) = n * (n-1) f(n)=n(n1),返回条件就为n=1时 f ( n ) = 1 f(n)=1 f(n)=1
  2. 斐波那契数列。
    在这里插入图片描述
    (虽然递归并不是一个高效求解的办法,由此引申出对递归的优化,比如剪枝,记忆化搜索等等,后面的系列会详细介绍)
// 递归
int Fib(int i) {
    if (i < 2) {
        return i == 0 ? 0 : 1;
    }
    return Fib(i - 1) + Fib(i - 2);
}
  1. 再比如,求二叉树的最大深度等等
  2. 递归经典问题——汉诺塔等等,感兴趣的同学可以自行学习。

递归问题的一般步骤:

1. 推导问题能否通过求解其子问题得到答案
2. 思考递归结束条件
3. 编写递归函数,调用并返回结果

分治

分治,英文为divide and conquer,一句话概括分治的思想就是:当问题的规模很大不易求解时,我们可以将一个问题分解为k规模相同且较小的子问题,子问题相互独立且与原问题相同,然后递归求解这些子问题,从而合并得到原问题的解
如下图,在这里插入图片描述
这里就验证了我们上面说的,递归和分治真没半毛钱关系。只不过分治也是将问题分解为多个规模相同且独立的子问题,而从子问题的求解到合并,恰恰是递归所擅长干的事,这也就使递归和分治往往捆绑在一起,从而得到很多高效且清晰的算法!(比如常见的归并排序

易用分治的问题,往往具有如下性质:

  1. 该问题的规模缩小到一定性质就可以很容易解决。 比如归并排序,当规模缩小到只有两个元素,我们很容易就比较出谁大谁小了。
  2. 问题需具有最优子结构的性质。 也就是说,原问题的最优解一定包含子问题的最优解。(或者通俗的说,子问题每一步最优,合并得到的原问题一定也是最优的)
  3. 子问题可以合并得到原问题的解
  4. 子问题之间最好是独立的

分治举例

简单罗列下想到的几个例子,就不给出代码了,建议自己实现,帮助理解。

  1. 计算n次方。很容易想到考虑下奇偶,然后n划分为n/2 * n/2, n/2 分为n /4 * n/4…
  2. 归并排序,经典的分治思想的算法
  3. 棋盘覆盖
  4. 大整数乘法
  5. 汉诺塔
  6. etc…

致谢

  1. 递归 & 分治
  2. 动漫算法 写的很有意思
  3. 里面有不错的例题
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Marcus-Bao

万水千山总是情,只给五角行不行

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值