从零学Java(30)之递归
Posted 编程界明世隐
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从零学Java(30)之递归相关的知识,希望对你有一定的参考价值。
作者简介
作者名:编程界明世隐
简介:CSDN博客专家,从事软件开发多年,精通Java、javascript,博主也是从零开始一步步把学习成长、深知学习和积累的重要性,喜欢跟广大ADC一起打野升级,欢迎您关注,期待与您一起学习、成长、起飞!
导航
✪ 从零学Java系列目录索引
◄上一篇【29】方法重载
►下一篇【31】构造方法
引言
♀ 小AD:明哥,我看你昨晚12点都还在线,这是要冲最强王者吗?
♂ 明世隐:是啊,然而血炸
♀ 小AD:什么血炸,被小学生安排了?
♂ 明世隐:算是也不全是,自己也SB😡!
♀ 小AD:那是怎么一回事呢?
♂ 明世隐:就是10点开始,我晋级赛,赢了就上王者了,所以我就说赢一把上去就睡觉,然后噩梦就开始了,一直输!
♀ 小AD:这不是正常吗,谁晋级赛没炸过?
♂ 明世隐:所以呢,我就减标准了,说赢一把就睡觉,还真邪了门了,到快一点才赢一把,你敢信?一片红,当时一口老血差点喷在屏幕上。
♀ 小AD:哥,保证身体,这不算什么,大仙也经历过!
♂ 明世隐:所以我今天也想明白了,也很正常,就是当时血气上涌,当时气得咬牙切齿的!
♀ 小AD:习惯就好!
♂ 明世隐:对,心态好很重要。
♀ 小AD:那心态好的明哥是不是要带我上分!
♂ 明世隐:我好像不经意间被装进去了?
♀ 小AD:哪有的事?每次你还不是有条件的,今天整什么呢?
♂ 明世隐:就整个递归吧。
递归的定义
程序调用自身的编程技巧称为递归,通俗的说就是函数本身自己调用自己。
- 为什么要用递归:递归的目的是简化程序设计,使程序易读。
- 递归的弊端:虽然非递归函数效率高,但较难编程,可读性较差。递归函数的缺点是增加了系统开销,也就是说,每递归一次,栈内存就多占用一截。
- 递归会增加内存开销,所以能不用递归的时候尽量别用。
- 递归一定要有正确的结束条件,如果没有正确的结束条件,一定会发生内存溢出错误。
- 即使有正确的结束条件,递归也有可能会发生内存溢出错误,因为可能递归太深了。
♀ 小AD:自己调用自己?日本人?
♂ 明世隐:你这么污的吗?我真高看你呀,老大。
♀ 小AD:你就说是不是这个意思。
♂ 明世隐:你说的都对,举个例子,我们人类的也可以用递归来描述。
♀ 小AD:怎么描述?
♂ 明世隐:我们每天都在做一些事情(把每天做的事情,看做活着),就是说每天起床活着,直到有一天goodbye了,就退出递归了。是不是可以看做是一个递归。
♀ 小AD:好像有一点道理,但也不是特别明白;还有就是明哥你举的例子,是不是太残暴了一些,不想往这方面想。
♂ 明世隐:行,那我们就举个程序上简单的例子来理解一下。
递归基础实例
- 创建一个类和一个doSome方法
public class Recursion
public static void main(String[] args)
doSome();
public static void doSome()
System.out.println("doSome start");
System.out.println("doSome end");
♀ 小AD:明哥这个我知道,就是在main方法调用doSome方法,然后会打印doSome start和doSome end
♂ 明世隐:是的,确实以你现在的水平,一看就明白,我们运行一下
运行结果:
doSome start
doSome end
♀ 小AD:明哥,你之前谁递归就是自己调用自己?那是不是在doSome方法调用doSome方法?
♂ 明世隐:对,就这个意思,加入代码并调用试试。
♀ 小AD:好滴!
- 加入递归调用
public class Recursion
public static void main(String[] args)
doSome();
public static void doSome()
System.out.println("doSome start");
doSome();
System.out.println("doSome end");
运行此代码:
♂ 明世隐:你看到了什么?
♀ 小AD:就一直打印 doSome start
♂ 明世隐:还有呢?
♀ 小AD:程序报错,然后退出了!
♂ 明世隐:你知道为什么会退出吗?
♀ 小AD:就是报错了呗!
♂ 明世隐:废话,要你说,为啥会报错?
♀ 小AD:不懂,我就看到一直打印
♂ 明世隐:有看到doSome end这句话的打印吗?
♀ 小AD:没有看到。
♂ 明世隐:没看到是不是意味着doSome这个方法没有结束?我们知道程序是按顺序一行一行的执行代码,执行完最后一行就退出,这里的doSome方法一直没有执行完。
♀ 小AD:嗯,好像是一直在执行。
♂ 明世隐:因为执行完打印doSome start就再次调用 doSome 方法,新的调用doSome 执行完打印doSome start,又接着调用doSome方法,并且是不停的调用,所以程序没有结束的时候,就会一直调用一直调用。指导内存溢出,程序终止。
♀ 小AD:就跟开车一样,一直踩着油门,然后车没油熄火了?
♂ 明世隐:你开的什么车?
♀ 小AD:不不不,明哥,我就打个比方,我没驾照呢还
♂ 明世隐:打比方也不行,比方是我朋友!你凭什么打他。
♀ 小AD:别整了明哥,我想好好学习。。。😱
♂ 明世隐:😂😂😂
- 加入退出条件(改造一下代码)
设定执行次数,然后执行完成退出
public class Recursion
private static int count = 10;
public static void main(String[] args)
doSome();
public static void doSome()
if (count == 0)
return;
count--;
System.out.println("doSome start");
doSome();
System.out.println("doSome end");
运行结果:
doSome start
doSome start
doSome start
doSome start
doSome start
doSome start
doSome start
doSome start
doSome start
doSome start
doSome end
doSome end
doSome end
doSome end
doSome end
doSome end
doSome end
doSome end
doSome end
doSome end
♀ 小AD:明哥这个我看明白了,就是每执行一次doSome方法就count递减1,这样count就会一直递减到0,到0以后,判断符合条件就用return来退出。
♂ 明世隐:👍👍
♀ 小AD:退出我知道,那程序世怎么一步步把完成退出的呢?
♂ 明世隐:程序是按栈的原则来执行,先进的后出。
♀ 小AD:不是先进先出?我们日常中排队不也是这样排的吗,我在前面先轮到我。
♂ 明世隐:但程序用的是栈,跟排队不一样的。
♀ 小AD:那我不理解
♂ 明世隐:我们来改造一下这段代码,再来分析它。
- 分析递归退出原理
public class Recursion
private static int count = 10;
public static void main(String[] args)
doSome();
public static void doSome()
int a = count;//记录此次的count值,用来打印
if (count == 0)
return;
count--;
System.out.println("doSome start " + a);
doSome();
System.out.println("doSome end " + a);
运行结果:
doSome start 10
doSome start 9
doSome start 8
doSome start 7
doSome start 6
doSome start 5
doSome start 4
doSome start 3
doSome start 2
doSome start 1
doSome end 1
doSome end 2
doSome end 3
doSome end 4
doSome end 5
doSome end 6
doSome end 7
doSome end 8
doSome end 9
doSome end 10
♂ 明世隐:你看10 是最先打开的start,却是最后打印的end
♀ 小AD:我还看到1 start最后打印的,却是第一个出来的。
♂ 明世隐:这就是先进后出的规则,举个例子,比如说我们堆砖块,从下往上堆,堆了10块,拿的时候一块块的往下拿,最下面的那块不就是最后才能拿到吗,能听明白不?
♀ 小AD:听明白了,明哥😄,但我想问问,我就想先拿最下面的怎么滴!🤣🤣
♂ 明世隐:你这得按人家的规则办事呀,程序就是这么设计的!那我问你,你输一盘掉一颗星星,赢一盘加一颗星,要按你的规则,反过来,输一盘加一个星星,你不是早就王者了?
♀ 小AD:你。。。,我打你哦,还人身攻击呢。
♂ 明世隐:所以你得按规则来,否则程序就不对,或者程序崩溃,比如刚这个实例,如果没有退出条件,就得崩溃。
- 有正确的退出条件,但是程序执行太深
修改 private static int count = 10000; 或者100000 甚至更多,也会内存溢出。
♀ 小AD:这个就是说明程序退出条件虽然正确,但是执行太多次了,也会报错吧。
♂ 明世隐:是这个意思,就是说理论上行得通,但是要根据实际情况。
♀ 小AD:比如理论上人类可以跑马拉松,但是我超过1000米,不停下来就得躺地下。
♂ 明世隐:很灵性,悟性不错!
♀ 小AD:所以这个退出条件设置的太大了,方法一直执行,最后内存溢出了。
♂ 明世隐:正解,所以前面我们说能不用递归的时候就不用,毕竟会可能会有些问题会发生。
♀ 小AD:那你还给我讲递归干嘛,不用我就不学了呗。
♂ 明世隐:我的意思是能不用就不用,并不是说不能用,有些情况用递归就非常好用,要根据实际情况。
♀ 小AD:哦这样的呀!
递归经典实例
实例1:
阶乘计算
package demo.demo78;
public class Test1
//定义方法
int doFactorial(int n)
int result;
if (n == 1)
return 1;
//执行递归操作
result = doFactorial(n - 1) * n;
return result;
public static void main(String[] args)
int res = new Test1().doFactorial(5);
System.out.println(res);
运行结果:
120
实例2
递归函数算出1到n的和
package demo.demo78;
public class Test2
//定义方法
int doSum(int n)
int result;
if (n == 1)
return 1;
//执行递归操作
result = doSum(n - 1) + n;
return result;
public static void main(String[] args)
int res = new Test2().doSum(5);
System.out.println(res);
运行结果:
15
实例3
黄金分割数列,要求输出一个序列:0 1 1 2 3 5 8 13 21 34 …(每一个数为前两个数子之和,要求用递归函数)
package demo.demo78;
public class Test3
//定义方法
int doSum(int n)
int result;
if (n == 0)
return 0;
else if (n == 1)
return 1;
else if(n == 2)
return 1;
//执行递归操作
result = doSum(n - 1) + doSum(n - 2);
return result;
public static void main(String[] args)
Test3 test3 = new Test3();
int res=0;
for (int i = 0; i < 10; i++)
res = test3.doSum(i);
System.out.print(res+" ");
运行结果:
0 1 1 2 3 5 8 13 21 34
实例4
从一个数组中随机取元素存到新的数组,保证元素不重复,全部取完(注意,是随机哦,然后要用递归)
package demo.demo78;
import java.awt.List;
import java.util.Collections;
import java.util.Random;
public class Test4
static int[] arr = 1,3,5,7,9,10;
static int[] newArr = new int[arr.length];
//定义方法
int getElement()
//定义返回结果
int res=0;
//实例化随机对象
Random random = new Random();
//根据数组的长度随机下标
int index=random.nextInt(arr.length);
int temp = arr[index];
if(checkElement(temp))//如果重复了,则递归
//递归获取元素
temp = getElement();
//取到了正确结果
res = temp ;
//返回
return res;
//检查是否有重复元素
boolean checkElement(int e)
//循环新数组
for (int i = 0; i < newArr.length; i++)
if(newArr[i]==e)//有相同的表示重复了
return true;
//没有重复则返回false
return false;
public static void main(String[] args)
Test4 test3 = new Test4();
int res=0;
//每次循环取到一个元素
for (int i = 0; i < arr.length; i++)
res = test3.getElement();
newArr[i]=res;
//打印新数组
for (int i = 0; i < newArr.length; i++)
System.out.print(newArr[i]+" ");
运行结果(每次元素的顺序都不一样)
9 1 7 10 5 3
5 3 1 9 10 7
♂ 明世隐:这些是我知道的一些比较经典的递归例子,当然还要不少情况可以递归的。
♀ 小AD:也就是说递归要根据实际的情况用,并不是随便用的。
♂ 明世隐:嗯,情况就是这样,就是说递归我可以不用,但我不能不会,我不用和我不会用不是一回事。
♀ 小AD:懂了 。
♂ 明世隐:那就好,那今天就到此为止吧。
♀ 小AD:不不不,明哥,我还得写一段代码送给你。
♂ 明世隐:又要搞事情?
小AD给明哥举例
public class RecursionTest
private static int count = 0;
public static void main(String[] args)
System.out.println("明哥说,赢一盘就睡觉!");
game();
System.out.println("明哥终于赢了,可以睡觉了!");
public static void game()
if (count == 10)
return;
count++;
System.out.println("明哥开始游戏,第" + count + "盘");
System.out.println("很不幸,明哥第" + count + "盘游戏输了");
game();
运行一下:
明哥说,赢一盘就睡觉!
明哥开始游戏,第1盘
很不幸,明哥第1盘游戏输了
明哥开始游戏,第2盘
很不幸,明哥第2盘游戏输了
明哥开始游戏,第3盘
很不幸,明哥第3盘游戏输了
明哥开始游戏,第4盘
很不幸,明哥第4盘游戏输了
明哥开始游戏,第5盘
很不幸,明哥第5盘游戏输了
明哥开始游戏,第6盘
很不幸,明哥第6盘游戏输了
明哥开始游戏,第7盘
很不幸,明哥第7盘游戏输了
明哥开始游戏,第8盘
很不幸,明哥第8盘游戏输了
明哥开始游戏,第9盘
很不幸,明哥第9盘游戏输了
明哥开始游戏,第10盘
很不幸,明哥第10盘游戏输了
明哥终于赢了,可以睡觉了!
♀ 小AD:明哥,我这例子怎么样?
♂ 明世隐:啥也不说了,太狠了你,这不是揭我伤疤吗?
♀ 小AD:我不就是把昨天你的情况,用代码写了一遍吗?你就说代码写的好不好吧
♂ 明世隐:好,写的真好,拜拜了您!
♀ 小AD:明哥你刚才不说说你已经放下了吗。
♂ 明世隐:我放。。。。😡😡
♀ 小AD:😅😅😅
小结
这节学习了“ 递归 ”,希望能对大家有所帮助,请各位小伙伴帮忙 【点赞】+【收藏】+ 【评论区打卡】, 如果有兴趣跟小明哥一起学习Java的,【关注一波】不迷路哦。
评论区打卡一波让我知道你,明哥会持续关注你的学习进度哦!
导航
✪ 从零学Java系列目录索引
◄上一篇【29】方法重载
►下一篇【31】构造方法
热门专栏推荐
【1】Java小游戏(俄罗斯方块、飞机大战、植物大战僵尸等)
【2】JavaWeb项目实战(图书管理、在线考试、宿舍管理等)
【3】JavaScript精彩实例(飞机大战、贪吃蛇、验证码等)
【4】Java小白入门200例
【5】从零学Java、趣学Java
【6】Idea从零到精通
以上是关于从零学Java(30)之递归的主要内容,如果未能解决你的问题,请参考以下文章