教你精通Java语法之第十二章、递归

news2024/11/23 20:01:29

目录

一、递归

1.1递归的概念

1.1.1定义

1.1.2原理

1.1.3思路

1.2单路递归 

1.2.1阶乘

1.2.2正向输出数字 

1.2.3反向输出字符串

1.3多路递归

1.3.1斐波那契数列

1.3.2兔子问题

1.3.3青蛙爬楼梯

1.4汉诺塔问题

1.5猴子吃桃问题

1.6老鼠走迷宫问题

二、递归的时间复杂度


一、递归

1.1递归的概念

1.1.1定义

定义:递归是一种解决计算问题的方法,其中解决方案取决于同一类问题的更小子集。

比如单链表递归遍历的例子:

void f(Node node) {
    if(node == null) {
        return;
    }
    println("before:" + node.value)
    f(node.next);
    println("after:" + node.value)
}

说明:

  1. 自己调用自己,如果说每个函数对应着一种解决方案,自己调用自己意味着解决方案是一样的(有规律的)。

  2. 每次调用,函数处理的数据会较上次缩减(子集),而且最后会缩减至无需继续递归。

  3. 内层函数调用(子集处理)完成,外层函数才能算调用完成。

1.1.2原理

假设链表中有 3 个节点,value 分别为 1,2,3,以上代码的执行流程就类似于下面的伪码

// 1 -> 2 -> 3 -> null  f(1)

void f(Node node = 1) {
    println("before:" + node.value) // 1
    void f(Node node = 2) {
        println("before:" + node.value) // 2
        void f(Node node = 3) {
            println("before:" + node.value) // 3
            void f(Node node = null) {
                if(node == null) {
                    return;
                }
            }
            println("after:" + node.value) // 3
        }
        println("after:" + node.value) // 2
    }
    println("after:" + node.value) // 1
}

1.1.3思路

  1. 确定能否使用递归求解

  2. 推导出递推关系,即父问题与子问题的关系,以及递归的结束条件

  3. 深入到最里层叫做递

  4. 从最里层出来叫做归

  5. 在递的过程中,外层函数内的局部变量(以及方法参数)并未消失,归的时候还可以用到

1.2单路递归 

1.2.1阶乘

用递归方法求阶乘

  • 阶乘的定义 $n!= 1⋅2⋅3⋯(n-2)⋅(n-1)⋅n$,其中 n 为自然数,当然 0! = 1

 代码实现:

private static int fac(int num) {
    if (num < 1) {
        return 1;
    }
    return num * f(num - 1);
}

拆解伪码如下,假设 n 初始值为 3:

f(int num = 3) { // 解决不了,递
    return 3 * f(int num = 2) { // 解决不了,继续递
        return 2 * f(int num = 1) {
            if (num < 1) { // 可以解决, 开始归
                return 1;
            }
        }
    }
}

1.2.2正向输出数字 

public static void Print(int num){
        if(num>9){
            Print(num/10);
        }
        System.out.print(num%10);
    }

1.2.3反向输出字符串

用递归反向打印字符串,n 为字符在整个字符串 str 中的索引位置。

  • :n 从 0 开始,每次 n + 1,一直递到 n == str.length() - 1

  • :从 n == str.length() 开始归,从归打印,自然是逆序的

    public static void Print(String str,int index){
            if(index<str.length()-1){
                Print(str,index+1);
            }
            //System.out.println(str.substring(index,index+1));
            System.out.println(str.charAt(index));
        }

    拆解伪码如下,假设字符串为 "abc"。

    void reversePrint(String str, int index = 0) {
        void reversePrint(String str, int index = 1) {
            void reversePrint(String str, int index = 2) {
                void reversePrint(String str, int index = 3) { 
                    if (index == str.length()) {
                        return; // 开始归
                    }
                }
                System.out.println(str.charAt(index)); // 打印 c
            }
            System.out.println(str.charAt(index)); // 打印 b
        }
        System.out.println(str.charAt(index)); // 打印 a
    }

1.3多路递归

1.3.1斐波那契数列

概念:如果每个递归函数例包含多个自身调用,称之为 multi recursion

public static int fib(int num) {
    if (num == 0) {
        return 0;
    }
    if (num == 1) {
        return 1;
    }
    return fib(num - 1) + fib(num - 2);
}

1.3.2兔子问题

  • 第一个月,有一对未成熟的兔子(黑色,注意图中个头较小)

  • 第二个月,它们成熟

  • 第三个月,它们能产下一对新的小兔子(蓝色)

  • 所有兔子遵循相同规律,求第 n 个月的兔子数

分析

兔子问题如何与斐波那契联系起来呢?设第 n 个月兔子数为 f(n)

  • f(n) = 上个月兔子数 + 新生的小兔子数

  • 而【新生的小兔子数】实际就是【上个月成熟的兔子数】

  • 因为需要一个月兔子就成熟,所以【上个月成熟的兔子数】也就是【上上个月的兔子数】

  • 上个月兔子数,即 f(n-1)

  • 上上个月的兔子数,即 f(n-2)

f(n)=f(n-1)+f(n-2);

因此本质还是斐波那契数列,只是从其第一项开始

1.3.3青蛙爬楼梯

  • 楼梯有 n 阶

  • 青蛙要爬到楼顶,可以一次跳一阶,也可以一次跳两阶

  • 只能向上跳,问有多少种跳法

  • 因此本质上还是斐波那契数列,只是从其第二项开始

  • 对应 leetcode 题目 70. 爬楼梯 - 力扣(LeetCode)

1.4汉诺塔问题

该游戏是在一块铜板装置上,有三根杆(编号A、B、C),在A杆自下而上、由大到小按顺序放置64个金盘(如图1)。游戏的目标:把A杆上的金盘全部移到C杆上,并仍保持原有顺序叠好。操作规则:每次只能移动一个盘子,并且在移动过程中三根杆上都始终保持大盘在下,小盘在上,操作过程中盘子可以置于A、B、C任一杆上。

设移动盘子数为n,为了将这n个盘子从A杆移动到C杆,可以做以下三步:

(1)以C盘为中介,从A杆将1至n-1号盘移至B杆;

(2)将A杆中剩下的第n号盘移至C杆;

(3)以A杆为中介;从B杆将1至n-1号盘移至C杆。

递归法:

实际操作中,只有第二步可直接完成,而第一、三步又成为移动的新问题。以上操作的实质是把移动n个盘子的问题转化为移动n-1个盘,那一、三步如何解决?事实上,上述方法设盘子数为n, n可为任意数,该法同样适用于移动n-1个盘。因此,依据上法,可解决n -1个盘子从A杆移到B杆(第一步)或从B杆移到C杆(第三步)问题。现在,问题由移动n个盘子的操作转化为移动n-2个盘子的操作。依据该原理,层层递推,即可将原问题转化为解决移动n -2、n -3… … 3、2,直到移动1个盘的操作,而移动一个盘的操作是可以直接完成的。

//代码实现:
public static void Tower(int num,char A,char B,char C){
        //num表示盘子的个数,A,B,C表示A塔,B塔,C塔
        if(num==1){
            System.out.println("移动:"+A+"到"+C);
        }
        else{
            //如果有多个盘子,可以看成两个,最下面的,上面所有的盘子(num-1)
            //(1.)先移动上面所有的盘子到B盘,借助C盘
            Tower(num-1,A,C,B);
            //(2.)把最下面的盘移动到C盘
            System.out.println("移动:"+A+"到"+C);
            //(3.)再把B塔上的盘子移动到C盘,借助A盘
            Tower(num-1,B,A,C);
        }
    }

1.5猴子吃桃问题

猴子吃桃子问题:     有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个! 以后每天猴子都吃其中的一半,然后再多吃一个。当到第10天时, 想再吃时(即还没吃),发现只有1个桃子了。问题:最初共多少个桃子?

//代码实现
	//猴子吃桃问题
    //day10:1个桃子
    //day9:(day10+1)*2
    //day8:(day9+1)*2
public static int Peach(int day){
        if(day<0||day>10){
            return -1;
        }
        if(day==10){
            return 1;
        }
        else if(day>=1&&day<=9){
            return (Peach(day+1)+1)*2;
        }
        return -1;
    }

1.6老鼠走迷宫问题

目标:小球从1-1走到6-5

 迷宫问题:

1.小球得到的路径,和程序员设置的找路策略有关:找路的上下左右的先后顺序

//代码实现
public static void main(String[] args) {
    	//思路:
        //1.设置迷宫,通过二维数组表示int[][] map=new int[8][7];
    	//2.规定:0表示可以走,1表示障碍物
        int[][] map=new int[8][7];//8行7列
        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[i].length; j++) {
                //设置最上面一行和最下面一行为1
                map[0][j]=1;
                map[7][j]=1;
                if(i>=1&&i<7){
                    //设置最右面和最左面一列为1
                    map[i][0]=1;
                    map[i][6]=1;
                }
            }
        }
        //输出迷宫
        disPlay(map);
        //设置障碍物
        map[2][1]=1;
        map[2][2]=1;
        disPlay(map);
        //找路
        findWay(map,1,1);
        disPlay(map);
    }
    public static void disPlay(int[][] map){
        System.out.println("==============================");
        for (int i = 0; i < map.length; i++) {
            for (int j = 0; j < map[i].length; j++) {
                System.out.print(map[i][j]+" ");
            }
            System.out.println();
        }
    }
    public static boolean findWay(int[][] map,int i,int j){
        //1.findWay方法找出迷宫的路径
        //2.如果找到,返回true,否则返回false
        //3.i,j表示老鼠的位置,初始化的位置为(1,1);
        //4.0表示可以走,1表示障碍物,2表示可以走,3表示走过但是走不通,是一条死路
        //5.当map[5][6]==2,说明找到了通路,此时就可以结束,否则继续找
        if(map[6][5]==2){//说明已经找到
            return true;
        }
        else{
            if(map[i][j]==0){ //表示当前位置可以走
                //假定该位置可以走通
                map[i][j]=2;
                //找路的逻辑:我的:右下左上(逻辑不通找路结果不同)或者【下右上左】
                if(findWay(map,i,j+1)){//右
                    return true;
                }
                else if(findWay(map,i+1,j)){//下
                    return true;
                }
                else if(findWay(map,i,j-1)){//左
                    return true;
                }
                else if(findWay(map,i-1,j)){//上
                    return true;
                }
                else{
                    map[i][j]=3;
                    return false;
                }
            }
            else{
                return false;
            }

        }
    }

2.存在回溯现象

//修改上述部分代码
        //设置障碍物
        map[3][1]=1;
        map[3][2]=1;
    	map[2][2]=1;
        disPlay(map);

二、递归的时间复杂度

递归式:                T(n) = aT(\frac{n}{b}) + f(n)

其中

  • T(n) 是问题的运行时间,n 是数据规模

  • a 是子问题个数

  • T(\frac{n}{b}) 是子问题运行时间,每个子问题被拆成原问题数据规模的 \frac{n}{b}

  • f(n) 是除递归外执行的计算

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

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

相关文章

从视频中截取gif怎么弄?三步简单完成视频转gif制作

电影、电视剧等短视频充斥着我们的生活&#xff0c;很多小伙伴会将这些视频中的有趣画面提取出来做成Gif动画表情包。那么&#xff0c;怎么才能从视频中提取gif动画呢&#xff1f; 一、使用什么工具才能从视频中提取gif呢&#xff1f; 通过使用GIF中文网这款专业的视频转gif&…

RabbitMQ (工作队列:Work Queues)

本章目录&#xff1a; 什么是Work Queues模拟场景&#xff0c;使用Work Queues官网文档&#xff1a;RabbitMQ tutorial - Work Queues — RabbitMQ 一、何为Work Queues 我们先看下它的结构图 显然&#xff0c;它与入门案例相比只是多了几个消费者。 以下是官方文档说明 In …

【目标检测】目标检测遇上知识图谱:Object detection meets knowledge graphs论文解读与复现

前言 常规的目标检测往往是根据图像的特征来捕捉出目标信息&#xff0c;那么是否有办法加入一些先验信息来提升目标检测的精准度&#xff1f; 一种可行的思路是在目标检测的输出加入目标之间的关联信息&#xff0c;从而对目标进行干涉。 2017年8月&#xff0c;新加波管理大学…

Vue——插槽

目录 插槽内容与出口​ 渲染作用域​ 默认内容​ 具名插槽​ 动态插槽名​ 作用域插槽​ 具名作用域插槽​ 高级列表组件示例​ 无渲染组件​ 插槽内容与出口​ 在之前的章节中&#xff0c;我们已经了解到组件能够接收任意类型的 JavaScript 值作为 props&#xff0c;…

微信小程序 | 基于ChatGPT实现电影推荐小程序

文章目录** 效果预览 **1、根据电影明星推荐2、根据兴趣标签推荐3、根据电影名推荐一、需求背景二、项目原理及架构2.1 实现原理&#xff08;1&#xff09;根据用户的兴趣标签&#xff08;2&#xff09;根据关联类似主题的题材&#xff08;3&#xff09;根据特定的电影明星2.2 …

IK集成ElasticSearch,IK分词器的下载及使用

IK集成ElasticSearch&#xff0c;IK分词器的下载及使用 下载ElasticSearch 8.7.0网址&#xff1a;Download Elasticsearch | Elastic 历史版本地址&#xff1a;Past Releases of Elastic Stack Software | Elastic 解压ElasticSearch 什么是IK分词器 分词∶即把一段中文或…

IO流基础

目录 1.FileOutPutStream字节输入流 1.1FileOutPutStream使用 1.1.1创建对象 FileOutPutStream fos new FileOutPutStream("路径或者File对象")&#xff1b; 1.1.2.写数据 调用write方法&#xff0c;参数是int类型&#xff0c;但传入文件中是asci…

【LeetCode: 剑指 Offer II 112. 最长递增路径 | 递归 | DFS | 深度优先遍历 | 记忆化缓存表】

&#x1f34e;作者简介&#xff1a;硕风和炜&#xff0c;CSDN-Java领域新星创作者&#x1f3c6;&#xff0c;保研|国家奖学金|高中学习JAVA|大学完善JAVA开发技术栈|面试刷题|面经八股文|经验分享|好用的网站工具分享&#x1f48e;&#x1f48e;&#x1f48e; &#x1f34e;座右…

海康工业相机网口相机丢包问题解决方法

​1.1 系统环境设置 1.1.1 网卡设置 网卡推荐使 Intel 芯片的独立千兆网口,例如 intel I350、I210 等芯片组网卡 设置网卡巨型帧为选择 9KB 或 9014 字节 *不同网卡类型,网卡属性有差异,需灵活参考 设置网卡接收与传输缓存区到最大(intel 网卡一般为 2048,realtek 一般…

Program tuning - Druid和Linux配合优化数据库连接池配置

Program tuning - Druid和Linux配合优化数据库连接池配置配置步骤1. 添加依赖2. 添加配置3. 启动监控界面常见问题输入地址之后&#xff0c;浏览器直接打印html代码&#xff0c;而不是登录框刷新页面不能重新加载数据调优步骤1. 开始压测2. 监控线程池状态3. 查看服务器状态结论…

算法 贪心5 || 435. 无重叠区间 763.划分字母区间 56. 合并区间 738.单调递增的数字 968.监控二叉树

435. 无重叠区间 和452. 用最少数量的箭引爆气球 思路是很相似的。本题按照左边排序或者按照右边排序都是可以的&#xff0c;最终目的都是为了让区间尽可能重叠。 1、按右边排序&#xff0c;排序完第一个元素的右边界一定是最小右边界。往下找第一个不与其重合的左边界&#x…

亚马逊云科技为全球的可持续发展进程做出贡献

可持续发展是一个涉及经济、环境和社会三个方面的复杂问题。经济发展必须在保护环境和社会公正的前提下进行&#xff0c;这样才能实现真正的可持续发展。为了实现这一目标&#xff0c;人们需要借助技术手段&#xff0c;更好地理解和解决环境和社会问题。 亚马逊云科技是全球领…

nssctf web入门(2)

目录 [SWPUCTF 2021 新生赛]easy_md5 [SWPUCTF 2021 新生赛]include [SWPUCTF 2021 新生赛]PseudoProtocols 这里通过nssctf的题单web安全入门来写&#xff0c;会按照题单详细解释每题。题单在NSSCTF中。 想入门ctfweb的可以看这个系列&#xff0c;之后会一直出这个题单的解…

到了这个年纪,就应该阅读Spring源码了,源码阅读指南-编译加运行

文章目录到了那个年纪&#xff0c;就应该阅读Spring源码了&#x1f604;第一步&#xff0c;clone&#x1f606;第二步&#xff0c;使用idea打开项目&#x1f60a;gradle介绍&#xff08;插叙手法&#xff09;&#x1f603;第三步&#xff0c;修改gradle的远程仓库地址&#x1f…

Day932.5个步骤,高效推动组件化架构重构 -系统重构实战

5个步骤&#xff0c;高效推动组件化架构重构 Hi&#xff0c;我是阿昌&#xff0c;今天学习记录都是关于5个步骤&#xff0c;高效推动组件化架构重构的内容。 项目的架构设计是一回事&#xff0c;代码落地又是另外一回事&#xff0c;很多架构设计最终都只是落在了 PPT 上。 一…

QT Graphics View坐标系转换

背景 在做绘图处理时&#xff0c;Scence作为场景&#xff0c;大小是无限的&#xff0c;而View作为一个观察镜头&#xff0c;观察范围是有限的。 那么有限的View观察无限的Scence区域&#xff0c;必然要选定一个观测锚点。 所以View具有一个centerOn(QPointF pos)函数&#xff…

Linux-初学者系列——篇幅1_文件管理命令(持续更新中)

Linux-初学者系列_篇幅1 文件管理命令-目录Linux-初学者系列_篇幅11.创建文件语法&#xff1a;示例&#xff1a;2.创建目录语法&#xff1a;示例&#xff1a;注意&#xff1a;常见错误&#xff1a;3.复制语法&#xff1a;示例&#xff1a;补充&#xff1a;4.移动语法&#xff1…

Vue|数据渲染

Vue 是如何将编译器中的代码转换为页面真实元素的&#xff1f;在Vue 中,自带了模板渲染,而模板的语法也非常简洁易懂。 精彩专栏持续更新↓↓↓ 微信小程序实战开发专栏 一. 数据渲染1.1 条件渲染v-ifv-show1.2 列表渲染v-for1.3 小结一. 数据渲染 1.1 条件渲染 vue条件渲染指…

3. 500 服务器异常 html

目录 1.效果图 2.code 1.效果图 2.code <!DOCTYPE html> <html><head><meta charset="utf-8"><title>500</title><style type="text/css">html,body {margin: 0;padding: 0;height: 100%;min-height: 450px;…

Git --- 常用命令、分支操作、团队协作机制

一、Git 概述 Git 是一个免费的、开源的分布式版本控制系统&#xff0c;可以快速高效地处理从小型到大型的各种项目 Git 易于学习&#xff0c;占地面积小&#xff0c;性能极快。它具有廉价的本地库&#xff0c;方便的暂存区域和多个工作流分支等特性 其性能优于 Subversion、…