​1.从现实中理解三个概念

可能很多人就已经接触了递归了,不过我敢保证很多人初学者刚开始接触递归的时候,是一脸懵逼的,我当初也是!

可能也有一大部分人知道递归,也能看的懂递归,但在实际做题过程中,却不知道怎么使用,有时候还容易被递归给搞晕。下面谈谈我的一些经验,希望能够给你带来一些帮助。

为了兼顾初学者,我会从最简单的题讲起!

1.1递归

我们应该都知道递归的概念,它的本质仍然是方法调用,不过是自己调用自己。这种例子在现实中也有很多的。
例如有一个笑话:

从前啊,有座山,山上有座庙,庙里有个老和尚和一个小和尚在讲故事,老和尚对小和尚说:

从前啊,有座山,山上有座庙,庙里有个老和尚和一个小和尚在讲故事,老和尚对小和尚说:

从前啊,有座山,山上有座庙,庙里有个老和尚和一个小和尚在讲故事,老和尚对小和尚说:

`````

再比如下面这张图:

原图地址:https://www.cnblogs.com/Pushy/p/8455862.html

这句吓得我抱起了抱着抱着抱着我的小鲤鱼的我的我的我。这就是递归的效果,可以通过代码来实现同样的效果:

 Recursion(depth) {    System.out.print('抱着');    if (!depth) {        System.out.print('我的小鲤鱼')    } else {        System.out.print(--depth);  // 递归调用    }    System.out.print('的我');}调用:System.out.print('吓得我抱起了');Recursion(2)

如果是代码的结构,就像下面这个样子,前面的每一层都去一模一样地调下一层,不同的只是输入和输出的参数不一样。

当然这个过程不能一直持续下去,一定要在满足某个要求之后返回结果的,所以递归里总是有一个终止的代码,但是递归过程中并不会调用,只有在最后一次还会。例如阶乘的递归调用结构如下,到了1之后就开始不断返回了:

1.2.递归和迭代的区别

迭代和递归又是啥区别呢?在前面我们有些题目我们会说递归法怎么样,迭代法怎么样,这里的迭代基本就是循环的意思,严格来说这是不对的。

递归和迭代的区别是啥呢?我们可以用动物繁殖来比较一下。我们知道蚯蚓砍成两段之后,两段会分别愈合伤口,发育成两个蚯蚓个体。之后我们可以再砍,这样就可以有很多蚯蚓,这个过程就是递归了。(当然不能一直砍,砍成泥就不行了)。递归的本质就是个树,每个结点又是一个子树的根结点,如下所示,当是叶子结点的时候就是要停止的时候。

递归的分支不一定是二叉,如果是一叉就是普通的递归,还可以是二叉,三叉,n叉都可以。

而迭代是什么过程呢?网上看到这样一个例子:假如你有一条哈士奇和一条中华田园犬,怎么让它们串出比较纯正的哈士奇呢?先让哈士奇与中华田园犬配对,生下小狗。再让哈士奇与小狗配对,当然要等小狗长大后。就这样一直让哈士奇与新生的小狗配对,一代一代地迭,最终你能得到比较纯正的哈士奇。

这里其实就说明了迭代的特点:上一次的输出是下一次输入的参数如下图。

正统的迭代其实是一个结构化的迭代器,我们后面单独设计一个迭代器。而且设计模式里有有个迭代器模式(没有递归模式),我们后面单独看。

因为上一次的输入是下一次输入的一个参数,因此通过迭代器,我们还能构造更强大状态转换关系——状态机,这个也是编译原理的基础。

1.3.递归和分治

那递归和分治又是什么关系呢?分治就是分而治之的意思,就是不断缩小问题的规模。最直接的就是二分查找,每次都和中间位置的元素比较,一次砍掉一半的数据量。再比如二叉树的查找,也是每次只访问左和右,这样就可以快速定位到目标元素(其实二分查找的过程图就是二叉树)。

递归每次只是调用自己,但是不一定能快速减少访问量,例如求阶乘的递归

f(n)=n*f(n-1)

这个执行效率还不如循环快。

分治则会快速减少数据,例如二分的核心代码是:

 int middle = (low+high)/2;        if(array[middle]>key){            //大于关键字            return  binSearch_2(key,array,low,middle-1);        }else if(array[middle]<key){            //小于关键字            return binSearch_2(key,array,middle+1,high);        }else{            return array[middle];        }

每次都将数量减少一半。

那分治属于递归吗?也不是,因为使用循环也可以实现分治,例如上面的二分使用循环,核心代码是这么写的:

 while (low <= high) {            middle = (low + high) / 2;            if (middle == key) {                return array[middle];            } else if (middle < key) {                low = middle + 1;            } else {                high = middle - 1;            }        }

所以,分治和递归是两种不同的思想。

2.深入理解递归

2.1递归的结构

简单的递归谁都知道,但是稍微复杂一些的特别容易想不明白。

递归必须具备两个条件,一个是调用自己,一个是有终止条件。这两个条件必 须同时具备,且一个都不能少。并且终止条件必须是在递归最开始的地方,也就是下面 这样:

public void recursion(参数0) {   if (终止条件) {    return ;  } recursion(参数1);}

不能把终止条件写在递归结束的位置,下面这种写法是错误的,因为这样会永远无法退出来,就会出现堆栈溢出异常 (StackOverflowError)。:

public void recursion(参数0) { recursion(参数1);  if (终止条件) {    return ;  } }

但实际上递归可能调用自己不止一次,并且很多递归在调用之前或调用之后都会有一些 逻辑上的处理,比如下面这样。

     public void recursion(参数0) {        if (终止条件) {            return;        }        //可能有一些逻辑运算1        recursion(参数1);        // 可能有一些逻辑运算1        recursion(参数2);        // ......3        recursion(参数n);        // 可能有一些逻辑运算    }

对于上面的操作,“可能有一些逻辑运算”的前三个位置如果分别加一个打印,就是二叉树的前序、中序和后序遍历。如果是n叉树就上面这个模块继续写。

我对递归的理解是先往下一层层传递,当碰到终止条件的时候会反弹,最终会反弹到调用处。

2.2 如何写递归方法

我们还是从简单的开始一步步分析递归的过程。

第一步:明确你这个函数想要干什么

对于递归,我觉得很重要的一个事就是,这个函数的功能是什么,他要完成什么样的一件事,而这个,是完全由你自己来定义的。也就是说,我们先不管函数里面的代码什么,而是要先明白,你这个函数是要用来干什么。

例如,我定义了一个函数

// 算 n 的阶乘(假设n不为0)int f(int n){}

这个函数的功能是算 n 的阶乘。好了,我们已经定义了一个函数,并且定义了它的功能是什么,接下来我们看第二步。

第二步:寻找递归结束条件

所谓递归,就是会在函数内部代码中,调用这个函数本身,所以,我们必须要找出递归的结束条件,不然的话,会一直调用自己,进入无底洞。也就是说,我们需要找出当参数为啥时,递归结束,之后直接把结果返回,请注意,这个时候我们必须能根据这个参数的值,能够直接知道函数的结果是什么。

例如,上面那个例子,当 n = 1 时,那你应该能够直接知道 f(n) 是啥吧?此时,f(1) = 1。完善我们函数内部的代码,把第二要素加进代码里面,如下:

// 算 n 的阶乘(假设n不为0)int f(int n){    if(n == 1){        return 1;    }}

有人可能会说,当 n = 2 时,那我们可以直接知道 f(n) 等于多少啊,那我可以把 n = 2 作为递归的结束条件吗?

当然可以,只要你觉得参数是什么时,你能够直接知道函数的结果,那么你就可以把这个参数作为结束的条件,所以下面这段代码也是可以的。

// 算 n 的阶乘(假设n>=2)int f(int n){    if(n == 2){        return 2;    }}

注意我代码里面写的注释,假设 n >= 2,因为如果 n = 1时,会被漏掉,当 n <= 2时,f(n) = n,所以为了更加严谨,我们可以写成这样:

// 算 n 的阶乘(假设n不为0)int f(int n){    if(n <= 2){        return n;    }}

第三步:找出函数的等价关系式

我们要不断缩小参数的范围,缩小之后,我们可以通过一些辅助的变量或者操作,使原函数的结果不变。

例如,f(n) 这个范围比较大,我们可以让 f(n) = n * f(n-1)。这样,范围就由 n 变成了 n-1 了,范围变小了,并且为了原函数f(n) 不变,我们需要让 f(n-1) 乘以 n。

说白了,就是要找到原函数的一个等价关系式,f(n) 的等价关系式为 n * f(n-1),即f(n) = n * f(n-1)。

找出了这个等价,继续完善我们的代码,我们把这个等价式写进函数里。如下:

// 算 n 的阶乘(假设n不为0)int f(int n){    if(n <= 2){        return n;    }    // 把 f(n) 的等价操作写进去    return f(n-1) * n;}

至此,递归三要素已经都写进代码里了,所以这个 f(n) 功能的内部代码我们已经写好了。

这就是递归最重要的三要素,每次做递归的时候,你就强迫自己试着去寻找这三个要素。

下一节,我们找几个典型的递归的题目再来感受一下上面这个过程。

递归、迭代和分治(1):递归相关推荐

  1. 递归——迭代是人,递归是神

    递归,就是自己调用自己. 首先,需要搞清楚函数是如何调用的.在执行被调函数之前,系统需要做3件事: 1.将实参,函数的返回地址等信息传递给被调函数保存. 2.为被掉函数的形参,局部变量分配空间 3.将 ...

  2. Java 二叉树基础概念(递归迭代)

    目录 1. 树型结构 1.1概念 1.2 概念(重要) 2. 二叉树(重点) 2.1 概念 2.2 二叉树的基本形态 2.3 两种特殊的二叉树 2.4 二叉树的性质 a.满二叉树 b.完全二叉树 2. ...

  3. 递归、迭代、分治、回溯、动态规划、贪心算法

    今天就简单来谈谈这几者之间的关联和区别 递归 一句话,我认为递归的本质就是将原问题拆分成具有相同性质的子问题. 递归的特点: 1.子问题拆分方程式,比如:f(n) = f(n-1) * n 2.终止条 ...

  4. 迭代是人,递归是神(迭代与递归的总结:比较)

    https://www.cnblogs.com/Renyi-Fan/p/7708012.html 在计算机编程实现中有常常两种方法:一曰迭代(iterate):二曰递归(recursion). 从&q ...

  5. 算法设计与分析(第三周)递归/迭代求Fibonacci前n项 【以及递归算法速度慢的原因】

    为了理解递归写的.真想求Fibonacci前n项,迭代是更好的选择,简单并且速度快.另外,注意一下溢出问题. 递归算法速度慢的原因 递归调用本身需要使用系统栈,每次分配函数内存以及栈都需要时间.不过这 ...

  6. leetcode144. 二叉树的前序遍历(递归+迭代)

    一:题目 二:上码 1:递归 class Solution {public:void preorder(TreeNode* root,vector<int>&v ) {if(roo ...

  7. [Leedcode][JAVA][第94/144/145题][前中后序遍历][递归][迭代][二叉树]

    [问题描述][] 前序遍历 先输出当前结点的数据,再依次遍历输出左结点和右结点 中序遍历 先遍历输出左结点,再输出当前结点的数据,再遍历输出右结点 后续遍历 先遍历输出左结点,再遍历输出右结点,最后输 ...

  8. 【Java数据结构与算法】第十七章 二分查找(非递归)和分治算法(汉诺塔)

    第十七章 二分查找(非递归)和分治算法(汉诺塔) 文章目录 第十七章 二分查找(非递归)和分治算法(汉诺塔) 一.二分查找 1.思路 2.代码实现 二.分治算法(汉诺塔) 1.概述 2.汉诺塔 一.二 ...

  9. [递归|迭代] leetcode 21 合并两个有序链表

    [递归|迭代] leetcode 21 合并两个有序链表 1.题目 题目链接 将两个升序链表合并为一个新的升序链表并返回.新链表是通过拼接给定的两个链表的所有节点组成的. 示例: 输入:1->2 ...

最新文章

  1. 当向后台插入或读取JSON数据遇见回车时
  2. VTK:Medical之TissueLens
  3. 2019牛客暑期多校训练营(第六场)E - Androgynos (构建自补图)
  4. Jquery1.6版本后attr的变化
  5. 技术面试问项目难题如何解决的_技术创新 | 降本增效,青海农信社项目小伙刻苦钻研解决联合支架设计难题!...
  6. c语言存储结构的实现,(C语言)栈的链式存储结构的实现
  7. 前端实践(3)——图像幻灯片
  8. app 要求字体使用楷体,使用字体包
  9. php 打印去掉页眉页脚,window.print打印 去掉页眉页脚及打印链接
  10. 苹果开发者中心上传APP屏幕快照
  11. 遗传算法GA原理及实现(python实现GA求解TSP代码)
  12. 【Codeforces Round #185 (Div. 2) D】Cats Transport
  13. 四大组件之activity生命周期探索
  14. ps软件怎么测试性能,实际性能测试:Photoshop处理
  15. jrtplib收发实例
  16. ipmitool 配置IP地址,账户,密码
  17. 请解释“amp;amp;”和“amp;amp;amp;amp;”的区别?“|”和“||”的区别?
  18. 【luogu1468】[Violet]蒲公英--求区间众数
  19. python机器识别追踪_多目标追踪器:用OpenCV实现多目标追踪(C++/Python)
  20. wf工作流java_WF Workflow 状态机工作流 开发

热门文章

  1. BatchNorm、LayerNorm、InstanceNorm、GroupNorm、WeightNorm
  2. (一)JPA的快速入门
  3. java重置按钮功能函数_Bootstrap按钮功能之查询按钮和重置按钮
  4. 为什么我们要掌握Linux系统编程?
  5. 个人随笔/记录一个博友推荐的截图工具《FSCapture》
  6. Excl2016密码忘记 破解办法
  7. sprint2的总结及团队贡献分
  8. HTML5+CSS3小实例:简约不简单的社交分享按钮
  9. Dubbo官方入门实例
  10. maven完全离线开发【解决jar包自动从网络下载导致打包失败】