数据结构与算法——Java实现 6.递归

要学会试着安静下来

                        —— 24.9.17

一、递归的定义

计算机科学中,递归是一种解决计算问题的方法,其中解决方案取决于同一类问题的更小子集

说明:

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

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

        ③ 内层函数调用(子集处理)完成,外层函数才能算调用完成(内层完成,外层才能完成——先处理子问题

二、递归的解题思路

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

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

① 深入到最里层叫

② 从最里层出来叫

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

④ 大问题慢慢递到小问题,再从小问题计算结果归回大问题

递的时候是顺序的,归的时候是逆序的

三、递归——习题

1.阶乘

用递归的方法求阶乘

阶乘的定义:n! = 1*2*.3*(n-2)*(n-1)*n,其中n为自然数,当然0! = 1

递推关系:f(n) = 1,n = 1;f(n) = n * f(n-1), n > 1

public class demo1Factorial {// 递归函数public static int factorial(int n) {if (n == 1){return 1;}return n * factorial(n-1);}public static void main(String[] args) {demo1Factorial demo1Factorial = new demo1Factorial();System.out.println(demo1Factorial.factorial(5));System.out.println(factorial(6));System.out.println(factorial(7));System.out.println(factorial(8));}
}

2.反向打印字符串

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

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

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

递推关系:f(m)=1rn1)osns.h()-1n=str.length()

注:因为是先递再归,所以反向打印字符串输出语句的位置应该放在调用递归函数之后

public class demo2ReversePrintStr {// 静态方法反向打印字符串public static void reversePrintStr(String str,int n) {if (n == str.length()){return;}// System.out.print(str.charAt(n)+" ");reversePrintStr(str,n+1);System.out.print(str.charAt(n)+" ");}public static void main(String[] args) {reversePrintStr("YYSHlcl",0);}
}

3.二分查找

输入元素,返回元素在有序数组中的位置

前提:待查找数组是有序数组

public class demo3BinarySearch {public static int binarySearch(int[] arr, int key) {int target = Finding(arr,key,0,arr.length-1);return target;}private static int Finding(int[] arr, int key,int i,int j) {if (i > j){return -1;}int m = (i + j) >>> 1;if (key == arr[m]) {return m;}else if (key < arr[m]) {j = m-1;return Finding(arr,key,i,j);}else {i = m+1;return Finding(arr,key,i,j);}}public static void main(String[] args) {int[] arr = {7,9,11,12,24,34,65,81,98};int res = binarySearch(arr, 81);System.out.println("结果为:"+res);System.out.println(binarySearch(arr, 7));System.out.println(binarySearch(arr, 9));System.out.println(binarySearch(arr, 81));}
}

4.递归冒泡排序

将数组划分成两部分 [0…j][j+1…a.length-1]

左边 [0…j] 是未排序部分

右边 [j+1…a.length-1]是已排序部分

未排序区间内,相邻的两个元素比较,如果前一个大于后一个,则交换位置

package Day7Recursion;import java.util.Arrays;public class demo4BubbleSort {public static void sort(int[] arr) {bubble(arr,arr.length-1);}// j代表未排序区域的右边界private static void bubble(int[] arr, int j) {if (j == 0){return;}for (int i = 0; i < j; i++) {if (arr[i] > arr[i + 1]) {int temp = arr[i];arr[i] = arr[i + 1];arr[i + 1] = temp;}}bubble(arr, j - 1);}public static void main(String[] args) {int[] arr = { 5, 4, 3, 2, 1 };System.out.println(Arrays.toString(arr));bubble(arr,arr.length-1);System.out.println(Arrays.toString(arr));sort(arr);System.out.println(Arrays.toString(arr));}
}

将已排序的部分省略,提高效率

    private static void adbubble(int[] arr, int j) {if (j == 0){return;}int x = 0;for (int i = 0; i < j; i++) {if (arr[i] > arr[i + 1]) {int temp = arr[i];arr[i] = arr[i + 1];arr[i + 1] = temp;x = i;}}adbubble(arr,x);}

 5.插入排序

插入排序(Insertion Sort)是一种简单直观的排序算法。它的工作原理是通过构建有序序列,对于未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。插入排序在实现上,通常采用in-place排序(即只需用到O(1)的额外空间的排序),因而在从后向前扫描过程中,找到排序位置后,需要将已排序元素逐步向后挪位,为最新元素提供插入空间。

插入排序:将元素放在一个已排序的元素队列中,使其仍然有序,类比于扑克牌,将插入位置找到后,插入元素

import java.util.Arrays;public class demo5InsertSort {public static void sort(int[] arr) {insertSort(arr,1);}// low代表未排序区域的下边界private static void insertSort(int[] arr,int low) {// 代表所有元素都已经被排序if (low == arr.length) {return;}int t = arr[low];int i = low - 1; // 已排序区域指针while (i >= 0 && arr[i] > t){ // 循环找到插入位置arr[i+1] = arr[i];i--;}// 找到插入位置arr[i+1] = t;insertSort(arr,low+1);}public static void main(String[] args) {int[] arr = {111,27,36,45,51,63,7,81,9};sort(arr);System.out.println(Arrays.toString(arr));}
}

四、多路递归

上面写到的递归都是只调用了一次递归函数,我们也称之为单路递归

如果每个递归函数包含多个自身的调用,我们称之为多路递归

1.斐波那契数列

思路:多路进行递归,将问题转化为子问题,逐步小化问题

public class demo1MoreFib {public static int fib(int n) {if (n==0){return 0;}if (n==1){return 1;}int x = fib(n-1);int y = fib(n-2);return x + y;}public static void main(String[] args) {int fib = fib(9);System.out.println("第九项斐波那契数列返回结果为:"+fib);}
}

斐波那契数列的时间复杂度推导:

2.兔子问题

第一个月,有一对未成熟的兔子

第二个月,它们成熟

第三个月,它们能产下一对新的小兔子

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

注意:注意停止迭代条件与开始数列条件,与斐波那契数列的变化

import java.util.Scanner;public class demo2FibRabbit {public static int fib(int n) {if (n==1||n==2){return 1;}return fib(n-1)+fib(n-2);}public static void main(String[] args) {System.out.println("请您输入你想要得知的月份:");Scanner sc = new Scanner(System.in);int month = sc.nextInt();int rab = fib(month);System.out.println("第"+month+"月的小兔子有"+rab+"只");}
}

3.青蛙爬楼梯

楼梯有 n. 阶

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

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

import java.util.Scanner;public class demo3frag {public static int fibFrag(int n) {if (n == 1){return 1;}if (n == 2){return 2;}return fibFrag(n-1) + fibFrag(n-2);}public static void main(String[] args) {Scanner sc = new Scanner(System.in);System.out.println("请输入您想要让青蛙上几楼");int n = sc.nextInt();int method = fibFrag(n);System.out.println("共用"+method+"种跳法");}
}

4.汉诺塔问题

 Tower of Hanoi,是一个源于印度古老传说:大梵天创建世界时做了三根金刚石柱,在一根柱子从下往上按大小顺序摞着 64 片黄金圆盘,大梵天命令婆罗门把圆盘重新摆放在另一根柱子上,并且规定:
        ①  一次只能移动一个圆盘
        ② 小圆盘上不能放大圆盘

思路

① 一个圆盘:圆盘1:a——>c

② 两个圆盘:圆盘1:a——>b、圆盘2:a——>c、圆盘1:b——>c

③ 三个圆盘:圆盘12:a——>b、圆盘3:a——>c、圆盘12:b——>c

④ 四个圆盘:圆盘123:a——>b、圆盘4:a——>c、圆盘123:b——>c

时间复杂度计算:T(n) = 2T(n-1) + c,T(1) = c
                             T(n) = c(2^n-1)

import java.util.LinkedList;public class demo4HanoiTower {// 三个LinkedList集合代表三根柱子static LinkedList<Integer> a = new LinkedList<>();static LinkedList<Integer> b = new LinkedList<>();static LinkedList<Integer> c = new LinkedList<>();// 类似于添加元素放到柱子上// 初始化柱子static void init(int n){for (int i = n; i >= 1 ; i--) {a.addLast(i);}}// 设计递归函数   n:圆盘个数 a:原始柱子 b:借用柱子 c:目标柱子static void move(int n,LinkedList<Integer> a,LinkedList<Integer> b,LinkedList<Integer> c){if (n==0){return;}// 将n-1个盘子由a借助c移动到b上move(n - 1, a, c, b);c.addLast(a.removeLast()); // 中间步骤Print();// 将n-1个盘子由b借用a移动到c上move(n - 1, b, a, c);}public static void Print(){System.out.println("————————————————————————");System.out.println(a);System.out.println(b);System.out.println(c);}public static void main(String[] args) {init(3);Print();move(3,a,b,c);}// 时间复杂度计算:T(n) = 2T(n-1) + c,T(1) = c//              T(n) = c(2^n-1)
}

5.杨辉三角

分析

行i,列j,那么[i][j]的取值应该为[ i-1 ][ j-1 ] + [ i-1 ][ j ]

当 j = 0 或 i = j 时,[ i ][ j ]的取值为-1

public class demo5YangHui {public static int element(int i,int j){if (j == 0||j == i){return 1;}return element(i - 1,j - 1)+element(i - 1,j);}// 打印空格private static void printSpace(int n,int i){int num = (n - 1 - i) * 2;for (int j = 0; j < num; j++) {System.out.print(" ");}}public static void Print(int n){for (int i = 0; i < n; i++) {printSpace(n,i);for (int j = 0; j <= i; j++) {System.out.printf("%-4d",element(i,j));}System.out.println();}}public static void main(String[] args) {Print(9);}}

 

6.杨辉三角——改进版1

用一个二维数组接收上次遍历的杨辉三角的数值,然后根据上一行的数据进行相加

用二维数组的空间复杂度换取递归的时间复杂度

public class demo6YangHuiEx1 {// 记忆法利用二维数组进行缓存,优化public static int element(int[][] triangle,int i,int j){if (triangle[i][j] > 0){return triangle[i][j];}if (j == 0 || i == j){triangle[i][j] = 1;return 1;}triangle[i][j] = element( triangle,i - 1,j - 1) + element(triangle,i - 1, j);return triangle[i][j];}// 打印空格 空格与高度和行号有关private static void printSpace(int n,int i){int num = (n - 1 - i) * 2;for (int j = 0; j < num; j++) {System.out.print(" ");}}public static void Print(int n){int[][] triangle = new int[n][];for (int i = 0; i < n; i++) {triangle[i] = new int[i+1];// 空格与高度和行号有关printSpace(n,i);for (int j = 0; j <= i; j++) {// 格式化输出 宽度为4 从左对齐System.out.printf("%-4d",element(triangle,i,j));}System.out.println();}}public static void main(String[] args) {Print(5);}
}

 

7.杨辉三角——改进版2

利用一维数组记忆存储,用少量的空间复杂度换取递归的时间复杂度

这种算法也称为动态规划算法

public class demo7YangHuiEx2 {// 记忆法利用一维数组进行缓存,优化// 打印空格 空格与高度和行号有关private static void printSpace(int n,int i){int num = (n - 1 - i) * 2;for (int j = 0; j < num; j++) {System.out.print(" ");}}private static void createRow(int[] row,int i){if (i == 0){row[0] = 1;return;}for (int j = i; j > 0 ; j--) {row[j] = row[j]+row[j-1];}}public static void Print(int n){int[] row = new int[n];for (int i = 0; i < n; i++) {createRow(row, i);printSpace(n,i);for (int j = 0; j <= i; j++) {// 格式化输出 宽度为4 从左对齐System.out.printf("%-4d",row[j]);}System.out.println();}}public static void main(String[] args) {Print(7);}
}

 

递归避免爆栈问题 —— 用循环代替递归

  • 循环的空间复杂度通常较低,主要取决于循环体内声明的局部变量和使用的数据结构。
  • 递归的空间复杂度较高,主要由递归调用的最大深度和每次递归所需的辅助空间决定。在设计和分析递归算法时,需要特别注意空间复杂度的控制,以避免栈溢出等问题。

五、递归时间复杂度的计算

其中,

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

        ② a是子问题个数

        ③ T(n/b)是子问题运行时间,每个子问题被拆成原问题数据规模的n/b

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

令x = logba,即 x = log子问题缩小倍数 子问题个数

则 

1.公式求解

例一

例二

例三

例四

例五

二分查找递归

    private static int Finding(int[] arr, int key,int i,int j) {if (i > j){return -1;}int m = (i + j) >>> 1;if (key == arr[m]) {return m;}else if (key < arr[m]) {j = m-1;return Finding(arr,key,i,j);}else {i = m+1;return Finding(arr,key,i,j);}}

子问题个数:a = 1

子问题数据规模缩小倍数:b = 2

除递归外执行的计算是常数级 c = 0

T(n) = T(n/2) + n^b

此时 x = c = 0,时间复杂度是T(n^0*logn) = T(logn) 

归并排序

    // 归并排序private void split(int arr[],int i,int j,int arr2[]) {if(j-i <= 1){return;}int m = (i+j)/2;// 递归split(arr,i,m,arr2);split(arr,m,j,arr2);// 合并merge(arr,i,m,j,arr2);}

子问题个数 a = 2

子问题数据规模缩小倍数 b = 2

除递归外,主要时间花在合并上,它可以用f(n) = n表示

T(n) = 2T(n/2) + n

此时 x = c = 1,时间复杂度是Θ(nlogn)

快速排序递归

    // 快速排序递归private static void algorithm quicksort(int[] arr,int low,int high){if (low >= high || low < 0 || high >= arr.length){return;}// 分区int p = partition(arr,low,high);// 递归quicksort(arr,low,p-1);quicksort(arr,p+1,high);}

子问题个数a = 2
子问题数据规模缩小倍数
        如果分区分的好,b = 2
        如果分区没分好,例如分区1的数据是0,分区2的数据是n-1

除递归外,主要时间花在分区上,它可以用f(n) = n表示

情况1

        分区分的好        T(n)=2T(n/2)+n
·        此时x=c=1,时间复杂度Θ(nlogn)
情况2

        分区没分好        T(n)=T(n-1)+T(1)+n

        不成比例,此时不能用主定理求解

2.展开求解

递归式不能用公式求解,每次递归时间复杂度不同,所以将式子展开,求出每次递归的时间复杂度,进行累计求出最后的时间复杂度

例一 递归求和

例二  递归冒泡排序

例三 递归快排

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

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

相关文章

2024/9/20 使用QT实现扫雷游戏

有三种难度初级6x6 中级10x10 高级16x16 完成游戏 游戏失败后&#xff0c;无法再次完成游戏&#xff0c;只能重新开始一局 对Qpushbutton进行重写 mybutton.h #ifndef MYBUTTON_H #define MYBUTTON_H #include <QObject> #include <QWidget> #include <QPus…

jdk版本更换以及遇到的问题略谈(以jdk1.8和jdk11为例)

目录 在我看来 遇到的问题 原因以及解决方法 方法一&#xff1a;禁止误改误删 方法二&#xff1a;bat文件驱动运行 方法三&#xff1a;cmd命令 方法四&#xff1a;修改注册表&#xff08;不推荐&#xff09; 最近在进行漏洞复现&#xff08;shiro550&#xff09;的时候&…

DAY20信息打点-红蓝队自动化项目资产侦察武器库部署企查产权网络空间

2.自动化-网络空间-AsamF 1.去GitHub上下载项目之后使用CMD打开 2.输入命令AsamF_windows_amd64.exe -v生成配置文件 3.AsamF会在~/.config/asamf/目录下生成config.json文件 C:\Users\Acer\.config\asamf 5.根据文档输入命令去查询所需信息&#xff08;已经没有用了&#x…

jeecg自定义sql查询,并使用高级查询的参数

jeecg框架的高级查询界面如下 后台代码&#xff1a; controller层&#xff1a; GetMapping(value "/list")public Result<IPage<Receiver>> queryPageList(Receiver receiver,RequestParam(name"pageNo", defaultValue"1") Integ…

进程和线程问题解答

线程和进程的概念、区别 进程是操作系统进行资源分配的基本单位&#xff0c;拥有独立的地址空间&#xff0c;包括代码、数据、堆、栈等。进程间的切换开销较大。 线程是进程中的一个执行单元&#xff0c;是系统中最小的执行单位&#xff0c;共享进程的资源&#xff0c;如代码…

Python类及元类的创建流程

Python类及元类的创建流程 代码运行结果再看type和object的关系和约定type和object具有的方法不一样看代码和运行结果&#xff0c;可以完全理解python的执行过程。再补充几点&#xff0c; 代码 class MetaCls(type):print(0>>>, MetaCls, 0)def __init__(self, name,…

基于微型5G网关的酒店服务机器人应用

智能机器人在酒店中已经越来越常见&#xff0c;并且也是提升客户体验、提高服务效率的重要工具。然而&#xff0c;尽管这些机器人在自动化服务方面可以发挥着重要作用&#xff0c;但它们仍然面临着一些通信、组网和在线管理方面的痛点。 针对这些难题&#xff0c;可以通过部署微…

Python边界值测试工具:生成指定大小文件

在进行软件测试的过程中&#xff0c;经常会有文件上传功能的需求&#xff08;例如头像上传、商品图上传等&#xff09;&#xff0c;这时候就需要考虑文件大小的边界值&#xff0c;例如只可上传1-2MB的图片&#xff0c;5-10MB的文件&#xff0c;想要验证需求的话&#xff0c;就需…

CVPT: Cross-Attention help Visual Prompt Tuning adapt visual task

论文汇总 当前的问题 图1:在VTAB-1k基准测试上&#xff0c;使用预训练的ViT-B/16模型&#xff0c;VPT和我们的CVPT之间的性能和Flops比较。我们将提示的数量分别设置为1、10、20、50,100,150,200。 如图1所示&#xff0c;当给出大量提示时&#xff0c;VPT显示了性能的显著下降…

电脑ip会因为换了网络改变吗

在当今数字化时代&#xff0c;IP地址作为网络世界中的“门牌号”&#xff0c;扮演着至关重要的角色。它不仅是设备在网络中的唯一标识&#xff0c;也是数据交换和信息传递的基础。然而&#xff0c;对于普通用户而言&#xff0c;一个常见的问题便是&#xff1a;当电脑连接到不同…

三菱变频器Modbus-RTU 通讯规格

能够从变频器的 RS-485 端子使用 Modbus-RTU 通讯协议&#xff0c;进行通讯运行和参数设定。 NOTE: 1、使用 Modbus-RTU 通讯协议时&#xff0c;请设定Pr.549 协议选择 “1” 2、从主机按地址0(站号0)进行hodbus-RTU通讯时&#xff0c;为广播通讯&#xff0c;变频器不向主机发…

QT编译后,如何手动运行,或复制到其他机器运行

编译后&#xff08;文件名叫Work.exe&#xff09;&#xff0c;通过QT功能&#xff0c;是可以成功运行的。如果在目录中双击&#xff0c;或复制到其他机器上运行&#xff0c;就会失败。怎么办&#xff1f; 打开命令窗口 运行命令 D:\Work\build\release>windeployqt Work.e…

写论文去哪个网站?2024最佳五款AI毕业论文学术网站

在2024年&#xff0c;AI技术在学术写作领域的应用已经变得越来越普遍。为了帮助学生和研究人员更高效地完成毕业论文的撰写任务&#xff0c;市场上涌现了许多优秀的AI论文写作工具。本文将重点推荐五款最佳的AI毕业论文学术网站&#xff0c;并特别强调千笔-AIPassPaper的优势。…

【应用开发三】 input子系统介绍

文章目录 1 名词解释2 输入设备编程框架2.1 input子系统2.2 读取数据流程2.3 input_event结构体2.3.1 type&#xff08;哪类事件&#xff09;2.2 code&#xff08;具体事件&#xff09;2.3 value&#xff08;数值&#xff09; 2.4 数据同步2.5 读取start input_event数据 1 名词…

【iOS】——YYModel源码总结

性能优化及优点 YYModel主要用于将JSON数据转换为模型对象&#xff0c;以及将模型对象转换为字典的库。相比于其他的数据转换库例如JSONModel&#xff0c;它更加的轻量级并且性能更高&#xff0c;因为它在很多地方做了优化&#xff1a; 通过CFDictionaryCreateMutable方法将数…

【数据结构与算法 | 灵神题单 | 二叉搜索树篇】力扣99, 1305, 230, 897

1. 力扣99&#xff1a;恢复二叉搜索树 1.1 题目&#xff1a; 给你二叉搜索树的根节点 root &#xff0c;该树中的 恰好 两个节点的值被错误地交换。请在不改变其结构的情况下&#xff0c;恢复这棵树 。 示例 1&#xff1a; 输入&#xff1a;root [1,3,null,null,2] 输出&…

【UWB无线载波通信技术】史上最详细解说!!

UWB定位技术的人员定位系统源码&#xff0c;高精度人员定位系统&#xff0c;自主研发&#xff0c;最高定位精度可达10cm&#xff0c;全套源码合作&#xff01; 目录 简介 基本原理 技术指标 应用 uwb定位系统应用场景 一、‌室内定位与导航‌&#xff1a; 二、‌特定行业…

如何快速修改CSDN代码块或者主题的字体颜色

第一步登录你的CSDN账号然后点击你的头像 第二步点击下拉框中的“内容管理” 第三步&#xff0c;点击“博客设置” 第四步&#xff0c;点击“等级”选择喜欢的主题和颜色 第五步&#xff0c;选择代码块的主题和颜色 最后保存刷新就可以了。

sqlite数据库设计工具

下载 开发环境 VS2022 + Qt5.14.2 CMake修改 add_subdirectory(sqlite3-cmake) include_directories(${CMAKE_SOURCE_DIR}/sqlite3-cmake/src) target_link_libraries(${PROJECT_NAME} sqlite3) 效果 参考 https://github.com/sqlitebrowser/sqlitebrowser

莱卡相机sd内存卡格式化了怎么恢复数据

在数字化时代&#xff0c;相机已成为我们记录生活、捕捉瞬间的重要设备。而SD内存卡&#xff0c;作为相机的存储媒介&#xff0c;承载着我们的珍贵记忆和重要数据。然而&#xff0c;有时由于误操作、系统错误或其他原因&#xff0c;我们可能会不小心格式化SD内存卡&#xff0c;…