递归就这么简单

递归介绍

本来预算此章节是继续写快速排序的,然而编写快速排序往往是递归来写的,并且递归可能不是那么好理解,于是就有了这篇文章。

在上面提到了递归这么一个词,递归在程序语言中简单的理解是:方法自己调用自己

递归其实和循环是非常像的,循环可以改写成递归,递归未必能改写成循环,这是一个充分不必要的条件。

  • 那么,有了循环,为什么还要用递归呢??在某些情况下(费波纳切数列,汉诺塔),使用递归会比循环简单很多很多
  • 话说多了也无益,让我们来感受一下递归吧。

我们初学编程的时候肯定会做过类似的练习:

  • 1+2+3+4+....+100(n)求和
  • 给出一个数组,求该数组内部的最大值

我们要记住的是,想要用递归必须知道两个条件:

  • 递归出口(终止递归的条件)
  • 递归表达式(规律)

技巧:在递归中常常是将问题切割成两个部分(1和整体的思想),这能够让我们快速找到递归表达式(规律)

一、求和

如果我们使用for循环来进行求和1+2+3+4+....+100,那是很简单的:

int sum = 0;for (int i = 1; i <= 100; i++) {sum = sum + i;}System.out.println("公众号:Java3y:" + sum);

前面我说了,for循环都可以使用递归来进行改写,而使用递归必须要知道两个条件:1、递归出口,2、递归表达式(规律)

首先,我们来找出它的规律:1+2+3+...+n,这是一个求和的运算,那么我们可以假设X=1+2+3+...+n,可以将1+2+3+...+(n-1)看成是一个整体。而这个整体做的事又和我们的**初始目的(求和)**相同。以我们的高中数学知识我们又可以将上面的式子看成X=sum(n-1)+n

好了,我们找到我们的递归表达式(规律),它就是sum(n-1)+n,那递归出口呢,这个题目的递归出口就有很多了,我列举一下:

  • 如果n=1时,那么就返回1
  • 如果n=2时,那么就返回3(1+2)
  • 如果n=3时,那么就返回6(1+2+3)

当然了,我肯定是使用一个最简单的递归出口了:if(n=1) return 1

递归表达式和递归出口我们都找到了,下面就代码演示:

递归出口为1:

public static void main(String[] args) {System.out.println("公众号:Java3y:" + sum(100));}/**** @param n 要加到的数字,比如题目的100* @return*/public static int sum(int n) {if (n == 1) {return 1;} else {return sum(n - 1) + n;}}

递归出口为4:

public static void main(String[] args) {System.out.println("公众号:Java3y:" + sum(100));}/**** @param n 要加到的数字,比如题目的100* @return*/public static int sum(int n) {//如果递归出口为4,(1+2+3+4)if (n == 4) {return 10;} else {return sum(n - 1) + n;}}

结果都是一样的。

二、数组内部的最大值

如果使用的是循环,那么我们通常这样实现:

int[] arrays = {2, 3, 4, 5, 1, 5, 2, 9, 5, 6, 8, 3, 2};//将数组的第一个假设是最大值int max = arrays[0];for (int i = 1; i < arrays.length; i++) {if (arrays[i] > max) {max = arrays[i];}}System.out.println("公众号:Java3y:" + max);

那如果我们用递归的话,那怎么用弄呢?首先还是先要找到递归表达式(规律)和递归出口

  • 我们又可以运用1和整体的思想来找到规律
    • 将数组第一个数->2与数组后面的数->{3, 4, 5, 1, 5, 2, 9, 5, 6, 8, 3, 2}进行切割,将数组后面的数看成是一个整体X={3, 4, 5, 1, 5, 2, 9, 5, 6, 8, 3, 2},那么我们就可以看成是第一个数和一个整体进行比较if(2>X) return 2 else(2<X) return X
    • 而我们要做的就是找出这个整体的最大值与2进行比较。找出整体的最大值又是和我们的**初始目的(找出最大值)**是一样的
    • 也就可以写成if( 2>findMax() )return 2 else return findMax()
  • 递归出口,如果数组只有1个元素时,那么这个数组最大值就是它了。

使用到数组的时候,我们通常为数组设定左边界和右边界,这样比较好地进行切割

  • L表示左边界,往往表示的是数组第一个元素,也就会赋值为0(角标为0是数组的第一个元素)
  • R表示右边界,往往表示的是数组的长度,也就会赋值为arrays.length-1(长度-1在角标中才是代表最后一个元素)

那么可以看看我们递归的写法了:

public static void main(String[] args) {int[] arrays = {2, 3, 4, 5, 1, 5, 2, 9, 5, 6, 8, 3, 1};System.out.println("公众号:Java3y:" + findMax(arrays, 0, arrays.length - 1));}/*** 递归,找出数组最大的值* @param arrays 数组* @param L      左边界,第一个数* @param R      右边界,数组的长度* @return*/public static int findMax(int[] arrays, int L, int R) {//如果该数组只有一个数,那么最大的就是该数组第一个值了if (L == R) {return arrays[L];} else {int a = arrays[L];int b = findMax(arrays, L + 1, R);//找出整体的最大值if (a > b) {return a;} else {return b;}}}

三、冒泡排序递归写法

在冒泡排序章节中给出了C语言的递归实现冒泡排序,那么现在我们已经使用递归的基本思路了,我们使用Java来重写一下看看:

冒泡排序:俩俩交换,在第一趟排序中能够将最大值排到最后面,外层循环控制排序趟数,内层循环控制比较次数

以递归的思想来进行改造:

  • 当第一趟排序后,我们可以将数组最后一位(R)和数组前面的数(L,R-1)进行切割,数组前面的数(L,R-1)看成是一个整体,这个整体又是和我们的**初始目的(找出最大值,与当前趟数的末尾处交换)**是一样的
  • 递归出口:当只有一个元素时,即不用比较了:L==R
public static void main(String[] args) {int[] arrays = {2, 3, 4, 5, 1, 5, 2, 9, 5, 6, 8, 3, 1};bubbleSort(arrays, 0, arrays.length - 1);System.out.println("公众号:Java3y:" + arrays);}public static void bubbleSort(int[] arrays, int L, int R) {int temp;//如果只有一个元素了,那什么都不用干if (L == R) ;else {for (int i = L; i < R; i++) {if (arrays[i] > arrays[i + 1]) {temp = arrays[i];arrays[i] = arrays[i + 1];arrays[i + 1] = temp;}}//第一趟排序后已经将最大值放到数组最后面了//接下来是排序"整体"的数据了bubbleSort(arrays, L, R - 1);}}

四、斐波那契数列

接触过C语言的同学很可能就知道什么是费波纳切数列了,因为往往做练习题的时候它就会出现,它也是递归的典型应用。

菲波那切数列长这个样子:{1 1 2 3 5 8 13 21 34 55..... n }

数学好的同学可能很容易就找到规律了:前两项之和等于第三项

例如:

 	1 + 1 = 22 + 3 = 513 + 21 = 34

如果让我们求出第n项是多少,那么我们就可以很简单写出对应的递归表达式了:Z = (n-2) + (n-1)

递归出口在本题目是需要有两个的,因为它是前两项加起来才得出第三项的值

同样地,那么我们的递归出口可以写成这样:if(n==1) retrun 1 if(n==2) return 2

下面就来看一下完整的代码吧:

public static void main(String[] args) {int[] arrays = {1, 1, 2, 3, 5, 8, 13, 21};//bubbleSort(arrays, 0, arrays.length - 1);int fibonacci = fibonacci(10);System.out.println("公众号:Java3y:" + fibonacci);}public static int fibonacci(int n) {if (n == 1) {return 1;} else if (n == 2) {return 1;} else {return (fibonacci(n - 1) + fibonacci(n - 2));}}

五、汉诺塔算法

图片来源百度百科:

玩汉诺塔的规则很简单:

  • 有三根柱子,原始装满大小不一的盘子的柱子我们称为A,还有两根空的柱子,我们分别称为B和C(任选)
  • 最终的目的就是将A柱子的盘子全部移到C柱子中
    • 移动的时候有个规则:一次只能移动一个盘子,小的盘子不能在大的盘子上面(反过来:大的盘子不能在小的盘子上面)

我们下面就来玩一下:

  • 只有一个盘子:
    • A柱子的盘子直接移动到C柱子中
    • 完成游戏
  • 只有两个盘子:
    • 将A柱子上的盘子移动到B柱子中
    • 将A柱子上的盘子移动到C柱子中
    • 最后将在B柱子的盘子移动到C柱子盘子中
    • 完成游戏
  • 只有三个盘子:
    • 将A柱子的盘子移动到C柱子中
    • 将A柱子上的盘子移动到B柱子中
    • 将C柱子盘子移动到B柱子盘子中
    • 将A柱子的盘子移动到C柱子中
    • 将B柱子的盘子移动到A柱子中
    • 将B柱子的盘子移动到C柱子中
    • 最后将A柱子的盘子移动到C柱子中
    • 完成游戏

.......................

从前三次玩法中我们就可以发现的规律:

  • 想要将最大的盘子移动到C柱子,就必须将其余的盘子移到B柱子处(借助B柱子将最大盘子移动到C柱子中[除了最大盘子,将所有盘子移动到B柱子中])[递归表达式]
  • 当C柱子有了最大盘子时,所有的盘子在B柱子。现在的目的就是借助A柱子将B柱子的盘子都放到C柱子中(和上面是一样的,已经发生递归了)
  • 当只有一个盘子时,就可以直接移动到C柱子了(递归出口)
    • A柱子称之为起始柱子,B柱子称之为中转柱子,C柱子称之为目标柱子
    • 从上面的描述我们可以发现,起始柱子、中转柱子它们的角色是会变的(A柱子开始是起始柱子,第二轮后就变成了中转柱子了。B柱子开始是目标柱子,第二轮后就变成了起始柱子。总之,起始柱子、中转柱子的角色是不停切换的)

简单来说就分成三步:

  1. 把 n-1 号盘子移动到中转柱子
  2. 把最大盘子从起点移到目标柱子
  3. 把中转柱子的n-1号盘子也移到目标柱子

那么就可以写代码测试一下了(回看上面玩的过程):

public static void main(String[] args) {int[] arrays = {1, 1, 2, 3, 5, 8, 13, 21};//bubbleSort(arrays, 0, arrays.length - 1);//int fibonacci = fibonacci(10);hanoi(3, 'A', 'B', 'C');System.out.println("公众号:Java3y" );}/*** 汉诺塔* @param n n个盘子* @param start 起始柱子* @param transfer 中转柱子* @param target 目标柱子*/public static void hanoi(int n, char start, char transfer, char target) {//只有一个盘子,直接搬到目标柱子if (n == 1) {System.out.println(start + "---->" + target);} else {//起始柱子借助目标柱子将盘子都移动到中转柱子中(除了最大的盘子)hanoi(n - 1, start, target, transfer);System.out.println(start + "---->" + target);//中转柱子借助起始柱子将盘子都移动到目标柱子中hanoi(n - 1, transfer, start, target);}}

我们来测试一下看写得对不对:

参考资料:

  • https://www.zhihu.com/question/24385418

六、总结

递归的确是一个比较难理解的东西,好几次都把我绕进去了....

要使用递归首先要知道两件事:

  • 递归出口(终止递归的条件)
  • 递归表达式(规律)

在递归中常常用”整体“的思想,在汉诺塔例子中也不例外:将最大盘的盘子看成1,上面的盘子看成一个整体。那么我们在演算的时候就很清晰了:将”整体“搬到B柱子,将最大的盘子搬到C柱子,最后将B柱子的盘子搬到C柱子中

因为我们人脑无法演算那么多的步骤,递归是用计算机来干的,只要我们找到了递归表达式和递归出口就要相信计算机能帮我们搞掂。

在编程语言中,递归的本质是方法自己调用自己,只是参数不一样罢了。

最后,我们来看一下如果是5个盘子,要运多少次才能运完:

PS:如果有更好的理解方法,或者我理解错的地方大家可以在评论下留言一起交流哦!

如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章,想要获取更多的Java资源的同学,可以关注微信公众号:Java3y

转载于:https://www.cnblogs.com/Java3y/p/8610134.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/252157.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

阿里云RPA(机器人流程自动化)干货系列之二:认识RPA(下)

2019独角兽企业重金招聘Python工程师标准>>> 导读&#xff1a;本文是阿里云RPA&#xff08;机器人流程自动化&#xff09;干货系列之二&#xff0c;主要介绍了RPA的发展齐纳经和主要使用场景有哪些&#xff0c;目前国内外主流的RPA厂商以及RPA的未来在哪。 一、RPA的…

C# 图片的读取

//图片转成二进制public byte[] GetPictureData(string imagepath){/**/根据图片文件的路径使用文件流打开&#xff0c;并保存为byte[] FileStream FileStream new FileStream(imagepath, FileMode.Open);byte[] byData new byte[FileStream.Length];FileStream.Read(byData,…

SDOI2010 地精部落

题目描述 传说很久以前&#xff0c;大地上居住着一种神秘的生物&#xff1a;地精。 地精喜欢住在连绵不绝的山脉中。具体地说&#xff0c;一座长度为N的山脉H可分为从左到右的N段&#xff0c;每段有一个[b][u]独一无二[/u][/b]的高度Hi&#xff0c;其中Hi是1到N之间的正整数。 …

Codechef Yet another cute girl

题意大概就是让你求一下[L,R]中的约数个数是素数的数的个数。 其中1<L<R<1e12,R-L<1e6. 然后我写了两种做法&#xff0c;第一种是可以直接搞出来L-R的约数个数&#xff0c;然后直接统计一下就好了。 这个的复杂度大致是 O((R-L) * log(R-L)) 第二种就是需要先发现满…

简单弄一个-个人主页

--- 整理一下已经发表的文章 JAVA基础 java基础数据结构之-红黑树(插入)java基础数据结构之-红黑树(删除)了解一下jdk动态代理的本质了解一下cglib动态代理的本质SpringBoot源码解析 前言&#xff1a;阅读springboot源码之前&#xff0c;最好对spring源码有一定的了解&#xff…

Halocn OCR识别入门学习

一、建立OCR库 dev_close_window() read_image(Image,OCR) get_image_size(Image,Width,Hight) dev_open_window(0,0,Width,Hight,black,Window) dev_display(Image)*字符处理 rgb1_to_gray(Image,ImageGray) *鼠标画你要找的roi区域 draw_rectangle1(Window,Row1,Column1,Row…

ctsc2009 移民站选址

分析&#xff1a;非常非常好的一道题&#xff01; 首先需要对问题进行转化&#xff1a; 行列无关&#xff0c;对于行单独处理&#xff0c;对于列单独处理必然存在一个最优方案使得每一个新站与旧站重合.转化1很显然&#xff0c;对于转化2&#xff0c;是一类非常经典的“中位数问…

Jenkins 安装与使用--实例

參考了博客Jenkins master在windows上安装 Jenkins的主要功能是监视反复工作的运行&#xff0c;比如软件project的构建详细地&#xff1a; *软件的持续构建和測试 本质上提供了一个易于使用的持续集成系统。使得开发者更easy地将改变集成到project中。使得用户更easy获得一个…

后端项目搭建技术栈

Koa2&#xff1a;koa-bodyparser koa-router koa-session koa-corsTypeScript数据库&#xff1a;Mysql &#xff08;库&#xff1a;Sequelize&#xff09;表单验证库&#xff1a;Joi

C# 实体类几种深拷贝的方法——解决关于对象赋值,A=B,A改变,B也改变问题

几种常见的深拷贝方式 1、利用反射实现 public static T DeepCopyByReflection<T>(T obj) {   if (obj is string || obj.GetType().IsValueType)   return obj; object retval Activator.CreateInstance(obj.GetType());   FieldInfo[] fields obj.GetType().…

Hadoop学习之路(九)HDFS深入理解

HDFS的优点和缺点 HDFS的优点 1、可构建在廉价机器上 通过多副本提高可靠性&#xff0c;提供了容错和恢复机制 服务器节点的宕机是常态 必须理性对象 2、高容错性 数据自动保存多个副本&#xff0c;副本丢失后&#xff0c;自动恢复 HDFS的核心设计思想&#xff1a; 分散均匀…

关于Unity中的声音管理模块(专题七)

声音的要素 1: 音频文件AudioClip2: 音源AudioSource;3: 耳朵AudioListener;//全局只能有一个4: 2D/3D音频;//2D只是简单地播放声音&#xff0c;3D可以根据距离衰减音量 怎样听到声音&#xff1a; 创建一个节点&#xff0c;挂载AudioSource组件&#xff0c;AudioSource组件关联…

重启唯一的窗体实例,以及调用系统重启函数失败解决办法

1、修改Program.cs内的程序启动函数 static class Program{public static System.Threading.Mutex Instance;/// <summary>/// 应用程序的主入口点。/// </summary>[STAThread]static void Main(){Application.EnableVisualStyles();Application.SetCompatibleTe…

ThreadLocal可能引起的内存泄露

threadlocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用以后,map里面的value却没有被回收.而这块value永远不会被访问到了. 所以存在着内存泄露. 最好的做法是将调用threadlocal的remove方法. 在threadlocal的生命周期中,都存在这些引用. 看下图: 实线代表强引…

codevs 1576 最长严格上升子序列

题目链接&#xff1a;http://codevs.cn/problem/1576/题目描述 Description给一个数组a1, a2 ... an&#xff0c;找到最长的上升降子序列ab1<ab2< .. <abk&#xff0c;其中b1<b2<..bk。 输出长度即可。 输入描述 Input Description第一行&#xff0c;一个整数N。…

nginx服务器开启缓存、反向代理

一、反向代理配置 1、反向代理服务器配置如下 反向代理就是需要这一行proxy_pass来完成。当我们要访问后端web服务器的时候&#xff0c;我们只需要访问代理服务器就可以了&#xff0c;此时代理服务器就充当后端web服务器的角色。proxy_pass依赖的模块是&#xff1a; 至于后两行…

Halcon:区域特征:select_shape(Regions : SelectedRegions : Features, Operation, Min, Max : )

Region特征一览&#xff1a; 特征 英 译 备注 area Area of the object 对象的面积 row Row index of the center 中心点的行坐标 column Column index of the center 中心点的列坐标 width Width of the region 区域的宽度 height Height of the…

Web应用主动侦测工具Skipfish

Web应用主动侦测工具SkipfishSkipfish是Kali Linux附带的一个主动Web应用侦测工具。该工具会首先尽可能获取所有网站路径&#xff0c;进行访问&#xff0c;然后根据返回的内容&#xff0c;检测是否存在漏洞。该工具采用字典爆破和网页爬行两种方式获取网站。一旦获取网页内容&a…

7步让你get首个数据科学实习

由于数据科学的庞大和复杂&#xff0c;如果你没有相关的实习经历的话&#xff0c;成为数据科学家的道路将会更加艰巨和困难。即使是经验丰富的人&#xff0c;实习也是转型进入数据科学领域的一种有效方式。 那么&#xff0c;寻找数据科学实习有哪些技巧&#xff1f;本文总结了数…

Halcon:Image、region、xld常用的处理

一、读取文件夹中的所有图片 list_files (C:/Users/fuping.liu/Desktop/槟榔有无头/有头, [files,follow_links], ImageFiles) tuple_regexp_select (ImageFiles, [\(tif|tiff|gif|bmp|jpg|jpeg|jp2|png|pcx|pgm|ppm|pbm|xwd|ima|hobj)$,ignore_case], ImageFiles)for Index :…