【算法】递归

news2025/1/17 21:51:37

目录

  • 1.递归概述
  • 2.何时使用递归
    • 2.1.定义是递归的
    • 2.2.数据结构是递归的
    • 2.3.问题的求解方法是递归的
  • 3.递归模型
  • 4.应用

本文参考:
《数据结构教程》第 5 版 李春葆 主编

1.递归概述

(1)在定义一个过程或函数时,出现直接或者间接调用自己的成分,称之为递归(recursion)。若直接调用自己,称之为直接递归(direct recursion);若间接调用自己,称之为间接递归(indirect recursion)

① 直接递归函数示例:求 n!,其中 n 为正整数。

//递归计算 n!
public int factorial(int n) {
    if (n == 1) {
        return 1;
    } else {
    	//直接调用 factorial(n - 1),属于直接递归
        return factorial(n - 1) * n;
    }
}

② 间接递归示例:

在这里插入图片描述

(2)在算法设计中,任何间接递归算法都可以转换为直接递归算法来实现,所以下面主要讨论直接递归。如果一个递归函数中递归调用语句是最后一条执行语句,则称这种递归调用为尾递归(tail recursion),例如上面的求 n! 的递归就属于尾递归。一般来说,递归算法与非递归算法的转换方式如下:

  • 尾递归算法:可以用循环语句转换为等价的非递归算法;
  • 其他递归算法:可以通过来转换为等价的非递归算法;

例如将上述递归求解 n! 转换为非递归的代码如下:

//非递归计算 n!
public int factorial(int n) {
    int res = 1;
    while (n > 1) {
        res *= n;
        n--;
    }
    return res;
}

(4)递归算法通常把一个大的复杂问题层层转化为一个或多个与原问题相似的规模较小的问题来求解,递归策略只需少量的代码就可以描述出解题过程中所需要的多次重复计算,大大减少了算法的代码量。一般来说,能够用递归解决的问题应该满足以下 3 个条件

  • 需要解决的问题可以转化为一个或多个子问题来求解,而这些子问题的求解方法与原问题完全相同,只是在数量规模上不同;
  • 递归调用的次数必须是有限的;
  • 必须有结束递归的条件来终止递归;

(5)递归的优缺点:

  • 优点:结构简单、清晰,易于阅读,方便其正确性证明;
  • 缺点:算法执行中占用的内存空间较多,执行效率低,不容易优化;

2.何时使用递归

在以下 3 种情况下常常要用到递归方法。

2.1.定义是递归的

有许多数学公式数列等的定义是递归的。例如,求 n! 和 Fibonacci 数列等。这些问题的求解过程可以将其递归定义直接转化为对应的递归算法。 例如求 Fibonacci 数列的递归算法如下:

//求 Fibonacci 数列的第 n 项,其中 n 为正整数
public int fibonacci(int n) {
    if (n == 1 || n == 2) {
        return 1;
    } else {
        return fibonacci(n - 1) + fibonacci(n - 2);
    }
}

2.2.数据结构是递归的

(1)有些数据结构是递归的。例如二叉树就是一种递归数据结构,其定义如下:

public class TreeNode {
	//节点值
    int val;
    //左子树
    TreeNode left;
    //右子树
    TreeNode right;
    
    TreeNode() {
    }
    
    TreeNode(int val) {
        this.val = val;
    }
    
    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

(2)对于递归数据结构,采用递归的方法编写算法既方便又有效、例如二叉树常见的前序遍历递归算法如下:

class Solution {
    // res 用于保存前序遍历的结果
    LinkedList<Integer> res = new LinkedList<>();
    
    public List<Integer> preorderTraversal(TreeNode root) {
        traverse(root);
        return res;
    }
    
    public void traverse(TreeNode root) {
        if (root == null) {
            return;
        }
        //将当前根节点的值加入到 res 中
        res.add(root.val);
        //递归遍历左子树
        traverse(root.left);
        //递归遍历右子树
        traverse(root.right);
    }
}

有关二叉树遍历的具体知识可以查看【算法】二叉树遍历这篇文章。

2.3.问题的求解方法是递归的

(1)有些问题的解法是递归的。典型的有 Hanoi 问题求解,该问题描述如下:

在这里插入图片描述

(2)设 hanoi(n, x, y, z) 表示将 n 个盘片从 x 塔座借助 y 塔座移动到 z 塔座上,具体的递归分解过程如下:
在这里插入图片描述
代码实现如下:

// hanoi(n, x, y, z) 表示将 n 个盘片从 x 塔座借助 y 塔座移动到 z 塔座上
public void hanoi(int n, char X, char Y, char Z) {
    if (n == 1) {
        cnt++;
        System.out.println("将第 " + n + " 个盘片从 " + X + " 移动到 " + Z);
    } else {
        //将 n - 1 个盘片从 x 塔座借助 z 塔座移动到 y 塔座上
        hanoi(n - 1, X, Z, Y);
        //将第 n 个盘片直接从 x 塔座移动到 z 塔座上
        System.out.println("将第 " + n + " 个盘片从 " + X + " 移动到 " + Z);
        //将 n - 1 个盘片从 y 塔座借助 x 塔座移动到 z 塔座上
        hanoi(n - 1, Y, X, Z);
    }
}

(3)设 hanoi(n, x, y, z) 的执行时间为 T(n),有 hanoi 递归算法得到以下递推式:

  • T(n) = 1,当 n = 1 时;
  • T(n) = 2T(n - 1) + 1,当 n > 1 时;

则有:
T(n) = 2T(n - 1) + 1 = 2(2T(n - 2) + 1) + 1
= 2 2 2^2 22T(n - 2) + 2 + 1
= 2 3 2^3 23T(n - 3) + 2 2 2^2 22 + 2 +1
= …
= 2 n − 1 2^{n - 1} 2n1T(1) + 2 n − 2 2^{n - 2} 2n2 + … + 2 2 2^2 22 + 2 + 1
= 2 n 2^n 2n - 1
= O( 2 n 2^n 2n)

3.递归模型

(1)递归模型是递归算法的抽象,它反映一个递归问题的递归结构。例如求 n! 递归算法对应的递归模型如下:

f(n) = 1				n = 1	//递归出口
f(n) = n * f(n - 1)		n > 1	//递归体

(2)一般地,一个递归模型是由递归出口递归体两部分组成,其中:

  • 递归出口确定递归到何时结束;
  • 递归体确定递归求解时的递推关系。

① 递归出口的一般格式如下:

f( s 1 s_1 s1) = m 1 m_1 m1

这里的 s 1 s_1 s1 m 1 m_1 m1 均为常量,有些递归问题可能有几个递归出口

② 而递归体的一般格式如下:

在这里插入图片描述

(3)递归思路

在这里插入图片描述

例如,统计全国 GDP:

在这里插入图片描述

(4)为了讨论方便,简化上述递归模型为:

在这里插入图片描述
求 f( s n s_n sn) 的分解过程如下:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

4.应用

(1)以 LeetCode 中的206.反转链表为例:

在这里插入图片描述

① 链表节点的定义如下,其数据结构是递归的。

 public class ListNode {
     int val;
     ListNode next;
     ListNode() {}
     ListNode(int val) { this.val = val; }
     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 }

② 这里假设 reverseList(head) 将以 head 为头节点的链表进行反转,那么使用递归解题的关键在于:假设链表的其余部分已经被反转,现在应该如何反转它前面的部分?假设链表为:

n 1 n_1 n1 n 2 n_2 n2 → … n k − 1 n_{k - 1} nk1 n k n_k nk n k + 1 n_{k + 1} nk+1 → … → n m n_m nm → ∅

若从节点 n k + 1 n_{k+1} nk+1 n m n_m nm 已经被反转(即执行 reverseList( n k + 1 n_{k+1} nk+1)),而我们正处于 n k n_k nk

n 1 n_1 n1 n 2 n_2 n2 → … n k − 1 n_{k - 1} nk1 n k n_k nk n k + 1 n_{k + 1} nk+1 ← … ← n m n_m nm

我们希望 n k + 1 n_{k+1} nk+1 的下一个节点指向 n k n_k nk。所以 n k n_k nk.next.next= n k n_k nk 。需要注意的是 n 1 n_1 n1 的下一个节点必须指向 ∅。如果忽略了这一点,链表中可能会产生环。

③ 具体的代码实现如下:

class Solution {
	public ListNode reverseList(ListNode head) {
		//递归终止条件
	    if (head == null || head.next == null) {
	        return head;
	    }
	    ListNode newHead = reverseList(head.next);
	    /*
	    	以 head->head1->null 为例:
			经过 head.next.next = head 之后,得到 head1->head->head1
			经过 head.next = null 之后,得到 head1->head->null
			这样便将 head 与 head1 进行了反转
		*/
	    head.next.next = head;
	    head.next = null;
	    return newHead;
	}
}

(2)大家可以去 LeetCode 上找相关的递归的题目来练习,或者也可以直接查看LeetCode算法刷题目录 (Java)这篇文章中的递归章节。如果大家发现文章中的错误之处,可在评论区中指出。

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

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

相关文章

【微服务】Elasticsearch文档索引库操作(二)

&#x1f697;Es学习第二站~ &#x1f6a9;Es学习起始站&#xff1a;【微服务】Elasticsearch概述&环境搭建(一) &#x1f6a9;本文已收录至专栏&#xff1a;微服务探索之旅 &#x1f44d;希望您能有所收获 一.索引库操作 索引库就类似数据库表&#xff0c;mapping映射就类…

DGIOT低代码场景部门的搭建过程

[小 迪 导读] : 通过低代码页面与konva 大屏的页面设计,围绕部门&#xff0c;实现应用场景快速搭建1.部门创建以及权限分配1.1 打开部门管理页面1.2新增部门1.3 权限分配&#xff0c;点击刚创建的部门&#xff0c;在菜单分配中选择总控台和设备管理(低代码平台会过滤掉非低代码…

Wandb:make visualization better than Tensorboard

Wandb&#xff1a;make visualization better than Tensorboard wandb :一个在线的可多人协作的多功能可视化工具包 我最开始使用的tensorboard&#xff0c;还写了一些相关tensorboard的脚本用于实验。tensorboard这里就不详细介绍了&#xff0c;相信大家都比较了解。直到尝试了…

【MySQL数据库入门】:表的约束

表的约束 真正约束字段的是数据类型&#xff0c;但是数据类型约束很单一&#xff0c;需要有一些额外的约束&#xff0c;更好的保证数据的合法性&#xff0c;从业务 逻辑角度保证数据的正确性。比如有一个字段是email&#xff0c;要求是唯一的。 表的约束很多&#xff0c;这里主…

版本管理之Git

一.版本控制器的方式1.1集中式版本控制工具集中式版本控制工具&#xff0c;版本库是集中存放在中央服务器的&#xff0c;team里每个人work时从中央服务器下载代 码&#xff0c;是必须联网才能工作&#xff0c;局域网或互联网。个人修改后然后提交到中央版本库。 举例&#xff1…

巧用回调函数解决微信小程序与后台数据交互出现的异步问题

问题描述 微信小程序端需要发送一个包含文字与图片的表单数据给后端&#xff0c;我一开始的思路是先上传图片得到临时的URL&#xff0c;后执行POST请求将表单数据发送给后端&#xff0c;但后端只能获取到文字&#xff0c;而图片URL却始终获取不到。 问题原因 注意看我上面的思路…

目标检测研究

传统的目标检测流水线 1.候选区域生成 通过滑动窗口选择感兴趣区域Rol;使用多尺寸的输入图像和多尺度的滑动窗口识别多尺度和不同比例的目标。 ⒉特征向量抽取 常用SIFT、 Harr、HOG、SURF。 3.区域分类 常用支持向量机。 结合集成、串联学习、梯度…

3D俯视角色割草游戏模板+视频教程,免费发布 | 一周精品推荐

大家好&#xff0c;我是晓衡。新年开工第一周&#xff0c;我就被热心的开发者们感动得热泪盈眶&#xff01;今天我冒死推荐几款 Creator 游戏开发资源&#xff0c;希望能对得起这些开发者们&#xff0c;同时也希望你能也有所收获。3D俯视角割草游戏视频源码B 站 UP 主『好巧啊c…

MyBatis 数据查询语句中有关于大于,小于的书写方法 及 查询时相关sql 关键字

前言 提示&#xff1a;这里记录的大概内容&#xff1a; MyBatis 数据查询语句中有关于大于&#xff0c;小于的书写方法 一、MyBatis MyBatis 本是 apache 的一个开源项目 iBatis, 2010 年这个项目由 apache software foundation 迁移到了 google code&#xff0c;并且改名为…

Python封装、继承和多态

Python 语言在设计之初&#xff0c;就定位为一门面向对象的编程语言&#xff0c;“Python 中一切皆对象”。同时&#xff0c;Python 也支持面向对象的三大特征&#xff1a;封装、继承和多态。 一、封装 封装&#xff08;Encapsulation&#xff09;&#xff0c;即在设计类时&am…

讲师邀请 | 在 DevData Talks,开放务实地聊聊研发效能!

什么是 DevData Talks&#xff1f; DevData Talks 是专注于研发效能实践经验与方法论的系列分享活动。 2022 年&#xff0c;我们既看到外部环境变幻莫测&#xff0c;也看到研发效能领域沉下心来稳步发展&#xff0c;从宏大的概念和价值&#xff0c;转向具体的问题&#xff0c…

若依框架代码自动生成器研究-表查询篇

最近生产环境用了一个开源系统&#xff1a;若依&#xff0c;其中有一个版块很有意思&#xff0c;很能提高生产效率: “代码生成器”。 其功能所处模块菜单为&#xff1a;系统工具->代码生成。我们来研究一下他的代码生成逻辑。 工具使用方法 1、建表 使用代码生成&#…

Python列表中你所不知道的事

1. 引言 目前&#xff0c;Python是世界上使用最广泛、最受欢迎的编程语言之一。Python丰富的功能性使它非常流行&#xff0c;因为我们可以使用它创建任何内容。我将在本博客中与大家分享关于Python列表的几条有趣的花絮。 闲话少说&#xff0c;我们直接开始吧&#xff01; 2.…

如何高薪入职心仪的公司

序 本文首发自&#xff1a;稀土掘金、思否 我们从几个问题开始入手&#xff0c;来看一下本博客是否适合你&#xff1a; 如果你想要换工作&#xff0c;但是&#xff1a;制作的简历平平无奇如果你想要换工作&#xff0c;但是&#xff1a;投放了的简历总是无法得到 [心仪公司] 的…

SpringBoot+Vue茶叶商城系统

简介&#xff1a;本项目采用了基本的SpringBootVue设计的茶叶商城系统。详情请看主要截图。经测试&#xff0c;本项目正常运行。本项目适用于Java毕业设计、课程设计学习参考等用途。 项目描述 项目名称SpringBootVue茶叶商城系统源码作者LHL项目类型Java EE项目 &#xff08;…

C#windows彩票信息管理

摘要&#xff1a;近年来&#xff0c;中国彩票行业已经进入市场急速扩张和加速上升的阶段&#xff0c;即开票占整个彩票销量的比率也将急剧上扬。自助售彩终端&#xff0c;这一崭新的售彩模式已被中国彩民接受&#xff0c;爆发点很快来临。到2020年&#xff0c;我国多功能彩票自…

百趣代谢组学文献分享:OnPLS方法在哮喘领域应用研究

百趣代谢组学文献分享&#xff0c;本周分享的文献题目为OnPLS-Based Multi-Block Data Integration: A Multivariate Approach to Interrogating Biological Interactions in Asthma&#xff0c;是由日本前桥群马大学创新研究中心Craig E. Wheelock教授课题组在2018年发表于Ana…

商业智能 BI 人员的六个Level,你到了哪一层?

现在商业智能 BI 行业的从业人员越来越多&#xff0c;但很多人对于自己的职业规划可能并不是特别的清晰&#xff0c;不知道在这个细分领域到底有多大的成长空间&#xff0c;未来大概可以走到哪一个层次。 今天大概介绍下这六个层次&#xff0c;可以是大多数从事商业智能 BI 工…

【计算机程序设计思想与方法】1 什么是计算?

计算是利用计算机解决问题的过程,计算机科学是关于计算的学问。 计算机科学家在用 计算机解决问题时形成了特有的思维方式和解决方法,即计算思维。 1.1 什么是计算? 1.1.1 计算机与计算 计算机是当代最伟大的发明之一。 自从人类制造出第一台电子数字计算机,迄今已近 …

面试题-Java集合常见问题

1 常见集合集合相关类和接口都在java.util中&#xff0c;主要分为三中List(列表)、Map(映射)和Set(集合)其中Collection是集合List、Set的父接口&#xff0c;它主要有两个子接口&#xff1a;List&#xff1a;存储的元素有序&#xff0c;可重复。ArrayList基于数组实现LinkedLis…