算法:图解递归算法的应用场景和使用途径

news2024/11/24 11:10:41

文章目录

  • 什么是递归?
  • 使用递归的原因?
  • 如何理解递归?
  • 递归的使用写法
  • 典型例题和分析
    • 汉诺塔问题
    • 合并两个有序链表
    • 反转链表
    • 两两交换链表中的节点
    • pow
  • 总结

什么是递归?

递归就是函数自己调用自己的情况,在二叉树,快排,归并中都有较为广泛的使用场景

使用递归的原因?

当一个问题可以被拆分成无数个子问题,而这些子问题的解决操作全部相同的时候,就可以使用递归

如何理解递归?

  • 从宏观上讲,递归是一个宏观的过程,具体表现在:
  1. 递归的细节展开图不应过多在意
  2. 把递归的函数看成是一个黑盒
  3. 相信黑盒可以完成你希望它完成的任务

递归的使用写法

  1. 找到相同的子问题
  2. 函数体内只写一个子问题的解决方式
  3. 找到递归的结束条件

典型例题和分析

汉诺塔问题

在这里插入图片描述
汉诺塔问题是非常经典的和递归相关的题,因此花时间进行此题的研究是有意义的,这里使用递归图解分析解决的方法:

解决递归问题都是从简单问题开始的,假设现在只有一个盘子:

在这里插入图片描述

这一步是浅显易懂的,只需要一步即可,如果此时变成两个盘子:

在这里插入图片描述

两个盘子的操作分为上面三步,此时还看不出规律,继续找规律:如果变成三个盘子

在这里插入图片描述

实际上这是有规律可循的,上面的七个步骤其实可以进行一定程度的简化:

在这里插入图片描述

是可以简化为下面三步的,而对于上图中的第一步,其实就是当盘子数量为2的时候的情况,因此就找到了规律:当有N个盘子的时候,就让最上面的N-1个盘子移动到辅助柱,让最后一个盘子移动到目标柱,再让N-1个盘子移动到目标柱,而如何把N-1个盘子移动到某个柱子这个操作就可以进入递归进行,因此递归的整体框架就构建出来了

class Solution 
{
public:
    void dfs(vector<int>& a, vector<int>& b, vector<int>& c,int n)
    {
        // 递归终止条件
        if(n==1)
        {
            c.push_back(a.back());
            a.pop_back();
            return;
        }

        // 1.把a上的n-1个盘子借助c移动到b
        dfs(a,c,b,n-1);

        // 2.把a底部移动到目标柱
        c.push_back(a.back());
        a.pop_back();
        
        // 3.把b上的柱子移动到c上
        dfs(b,a,c,n-1);
    }

    void hanota(vector<int>& a, vector<int>& b, vector<int>& c) 
    {
        // dfs中参数的意思:让a柱上的a.size()个盘子借助b柱移动到c柱
        dfs(a,b,c,a.size());
    }
};

合并两个有序链表

在这里插入图片描述
这是链表的经典题目,在之前是使用循环来解决的,实际上这里也可以使用递归来解决

那根据递归的使用条件,首先要找到子问题,从宏观上来讲,这个题的子问题就是,如果list1指向的值大于list2指向的值,就把list1指向的值拿出来,list1->next和list2继续排序,因此这样就找到了递归的子问题循环情况:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution 
{
public:
    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) 
    {
        if(list1==nullptr)
        {
            return list2;
        }
        if(list2==nullptr)
        {
            return list1;
        }
        if(list1->val <= list2->val)
        {
            list1->next=mergeTwoLists(list1->next,list2);
            return list1;
        }
        else
        {
            list2->next=mergeTwoLists(list1,list2->next);
            return list2;
        }
    }
};

反转链表

在这里插入图片描述

这也是链表的一道经典的题目,之前做这个题的时候选择的方法有两种,一种是让上面的链表的每一个节点进行头插,另外一种是让链表的每一个节点模拟这个逆置的过程,总体来说使用头插简单一些

这里使用递归的思想解决问题:

现在要返回的是逆置的链表,因此这里把head之后的部分看成一个整体:

要相信递归可以完成dfs的操作,可以把这些节点都反转过来并且返回新的头结点,我们定义一个newhead进行接收,接着要把头结点和head->next这部分进行一次反转即可

这样看很难理解,但如果换一种角度看,把这个链表想象成一个树,那么就是不断走近树的根,当遇到树的叶子的时候进行返回,从树的叶子开始不断操作,不断的让节点进行反转,其实宏观上讲是一个后序遍历

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution 
{
public:
    ListNode* reverseList(ListNode* head) 
    {
        if(head==nullptr || head->next==nullptr)
        {
            return head;
        }

        ListNode* newhead=reverseList(head->next);

        head->next->next=head;
        head->next=nullptr;

        return newhead;
    }
};

两两交换链表中的节点

在这里插入图片描述

传统方法不过多感受,直接迭代即可,可以多定义几个指针就容易写一些

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution 
{
public:
    ListNode* swapPairs(ListNode* head) 
    {
        if(head==nullptr || head->next==nullptr)
        {
            return head;
        }

        ListNode* newhead=new ListNode(0);
        newhead->next=head;
        ListNode* prev=newhead;
        ListNode* slow=prev->next;
        ListNode* fast=slow->next;

        while(slow && fast)
        {
            slow->next=fast->next;
            fast->next=slow;
            prev->next=fast;
            prev=slow;
            slow=prev->next;
            if(slow)
            {
                fast=slow->next;
            }
        }

        return newhead->next;
    }
};

下面重点还是进行递归的方法上:

和上一个题一样,利用递归来解决问题要站在宏观的角度,把除了前两个节点外的节点看成一个整体,讲它交给dfs,此时只需要相信dfs内部一定可以实现目的,可以实现两两交换的目的,再返回一个值,此时只需要把前两个节点和后面的节点连起来即可

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution 
{
public:
    ListNode* swapPairs(ListNode* head) 
    {
        if(head==nullptr || head->next==nullptr)
        {
            return head;
        }

        ListNode* tmp=swapPairs(head->next->next);

        ListNode* next=head->next;
        head->next=tmp;
        next->next=head;
        head=next;

        return head;
    }
};

pow

在这里插入图片描述

一个递归实现幂指数的问题,注意递归的结束条件即可

class Solution 
{
public:
    double myPow(double x, int n)
    {
        return n<0 ? 1.0 /pow(x,-(long long)n) : pow(x,n);
    }
    double pow(double x, int n) 
    {
        if(n==0)
        {
            return 1;
        }
        double tmp=pow(x,n/2);
        return n%2==0? tmp*tmp:tmp*tmp*x;
    }
};

总结

  • 循环和递归是如何进行的?他们之间有什么相同不同点?

其实不管是循环和递归,都是因为在代码的实现逻辑中出现了一部分内容,需要使用一块相同的逻辑,也就是一个重复的子问题,在这样的条件下就可以使用循环和递归进行

也就是说从某种意义来说,循环和递归是可以相互转换的,只是会涉及到实现难度的问题,比如:
对于要打印N个数,采用循环的方式:

int main()
{
	vector<int> nums{ 1,2,3,4,5,6,7,8,9 };
	for (int i = 0; i < nums.size(); i++)
	{
		cout << nums[i] << " ";
	}
	return 0;
}

也可以采用递归的方式

void print(vector<int>& nums,int i)
{
	if (i == nums.size())
	{
		return;
	}
	cout << nums[i] << " ";
	print(nums, i + 1);
}

int main()
{
	vector<int> nums{ 1,2,3,4,5,6,7,8,9 };
	//for (int i = 0; i < nums.size(); i++)
	//{
	//	cout << nums[i] << " ";
	//}
	print(nums,0);
	return 0;
}

这是对于一些比较简单的情况可以这样相互转换,在大多数情况中,想要实现递归和循环的相互转换不是一件容易的事

  • 什么时候用循环?什么时候用递归?

虽然以前从未思考过这些问题,但既然总结到这里就难免有这样的问题

递归和循环的本质的一个区别,其实和递归的展开图有关,对于一个递归的展开图来说,整体上的逻辑其实是对一棵树进行深度优先遍历,这个树有很多种,也许是二叉树也可能是多叉树,但按照逻辑来看递归展开图,每当走到头就返回…实际上这个操作就是一个很明显的深度优先遍历,可能是前序遍历,也可能是后序遍历,这个不是重点,而对于二叉树的遍历,想要将其转换为非递归的模式,虽不是不可以,但也会十分繁琐,因此通俗来讲当有多个分支情况的时候,选取递归来进行遍历是比较合适的

而循环可以看成是只有一条路的树,从头走到尾再返回

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

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

相关文章

Spring Bean 别名处理原理分析

今天来和小伙伴们聊一聊 Spring 中关于 Bean 别名的处理逻辑。 1. Alias 别名&#xff0c;顾名思义就是给一个 Bean 去两个甚至多个名字。整体上来说&#xff0c;在 Spring 中&#xff0c;有两种不同的别名定义方式&#xff1a; 定义 Bean 的 name 属性&#xff0c;name 属性…

[machine learning]神经网路初步 basic neural network

这一篇写的很差....我会找时间慢慢补充的 1.神经网络是什么 在上一篇关于逻辑回归的博客中,简单介绍了逻辑回归模型,对于监督学习来说,模型可以理解为一个模块/函数,在足够的数据训练以后,通过梯度下降等手段进行拟合,最终根据输入来预测输出结果. 这一个东西,我们可以称之为…

修改了字符集,好多软件不能正常使用,所以,慎重。。。。

这里&#xff0c;默认是没有选中的。所以&#xff0c;你千万不要随便就选中了。&#xff08;terminal里乱码的问题&#xff0c;可以通过命令&#xff1a; chcp 65001 解决&#xff09;。如果你执意选中了这里&#xff0c;重启之后&#xff0c;至少4个软件异常&#xff1a; 1、…

华为OD机试 - TLV解析Ⅰ(Java 2023 B卷 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、Java算法源码五、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#xff09;》。 …

集成图片验证码Kaptcha-完成登录验证功能

下面展示的是用SpringBoot集成Kaptcha&#xff0c;当然用其他框架也是一样的。 导入Kaptcha 导入pom.xml&#xff0c;下面得到二选一&#xff0c;建议用github的&#xff0c;比google的快一点 <dependency><groupId>com.github.penggle</groupId><arti…

2024腾讯校招后端面试真题汇总及其解答(三)

21【算法题】反转链表 题目: 给定单链表的头节点 head ,请反转链表,并返回反转后的链表的头节点。 示例 1: 输入:head = [1,2,3,4,5] 输出:[5,4,3,2,1]示例 2: 输入:head = [1,2] 输出:[2,1]示例 3: 输入:head = [] 输出:[]提示: 链表中节点的数目范围是 [0, 5…

Chrome 和 Edge 上出现“status_breakpoint”错误解决办法

文章目录 STATUS_BREAKPOINTSTATUS_BREAKPOINT报错解决办法Chrome浏览器 Status_breakpoint 错误修复- 将 Chrome 浏览器更新到最新版本- 卸载不再使用的扩展程序和应用程序- 安装计算机上可用的任何更新&#xff0c;尤其是 Windows 10- 重启你的电脑。 Edge浏览器 Status_brea…

基于 Flink CDC 构建 MySQL 和 Postgres 的 Streaming ETL

官方网址&#xff1a;https://ververica.github.io/flink-cdc-connectors/release-2.3/content/%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B/mysql-postgres-tutorial-zh.html官方教程有些坑&#xff0c;经过自己实测&#xff0c;记录个笔记。 服务器环境&#xff1a; VM虚拟机&am…

HarmonyOS 实现表单页面的输入,必填校验和提交

一. 样例介绍 本篇 Codelab 基于 input 组件、label 组件和 dialog 组件&#xff0c;实现表单页面的输入、必填校验和提交&#xff1a; 1. 为 input 组件设置不同类型&#xff08;如&#xff1a;text&#xff0c;email&#xff0c;date 等&#xff09;&#xff0c;完成表单页…

Burp插件HaE与Authz用法

HaE与Authz均为BurpSuite插件生态的一员&#xff0c;两者搭配可以避免“越权”、“未授权”两类漏洞的重复测试行为。&#xff08;适用于业务繁杂&#xff0c;系统模块功能多的场景&#xff09; 两个插件都可以在store里安装 安装完后&#xff0c;点击Filter Settings勾选Sho…

DSP_TMS320F28377D_算法加速方法4_C语言编程优化

前面3篇的优化思路是从硬件本身和函数库这些方向去加速&#xff0c; 本文则仅从代码本身的效率去考虑加速的方法。 1、用全局变量比用局部变量快 void testfunction1(){ // 局部变量int i;double s,a,b;a 1.023;b 12.23;for(i 0; i < 1000; i){s __divf32(a,b);} }int …

这3个教学难题,你中招了吗?

在当今教育领域&#xff0c;提高教育质量和学生学习成果是学校和教育机构的首要任务之一。教育管理者、教师和政策制定者都在寻求创新的方法来监督和改进教育过程。 在线巡课系统应运而生&#xff0c;成为教育界的一项重要工具&#xff0c;旨在帮助学校管理者更好地理解教育实践…

无涯教程-JavaScript - ISOWEEKNUM函数

描述 ISOWEEKNUM函数返回给定日期的年份的ISO周编号。 语法 ISOWEEKNUM (date)争论 Argument描述Required/OptionalDateDate is the date-time code used by Excel for date and time calculation.Required Notes Microsoft Excel将日期存储为连续数字,因此可以在计算中使…

【网络教程】记一次使用Docker手动搭建BT宝塔面板的全过程(包含问题解决如:宝塔面板无法开启防火墙,ssh,nginx等)

文章目录 准备安装安装宝塔面板开启ssh和修改ssh的密码导出镜像问题解决宝塔面板无法开启防火墙无法启动ssh设置密码nginx安装失败设置开机启动相关服务准备 演示的系统环境:Ubuntu 22.04.3 LTS更新安装/升级docker到最新版本升级docker相关命令如下# 更新软件包列表并自动升级…

期权交易策略及案例的基本策略有哪些?

目前我国上市交易的期权品种日益丰富&#xff0c;期权的基础的交易方法是建立相应头寸再反向平仓&#xff0c;赚取权利金差价&#xff0c;也可以持有期权到期行权。除了基础的交易方法之外&#xff0c;期权还有一些组合策略&#xff0c;下文介绍期权交易策略及案例的基本策略有…

K210-AI视觉

1、颜色识别 image.find_blobs( thresholds, invertFalse, roi, x_stride2, y_stride1, area_threshold10, pixels_threshold10, mergeFalse, margin0, threshold_cbNone, merge_cbNone)thresholds : 必须是元组列表。 [(lo, hi), (lo, hi), …, (lo, hi)] 定义你想追踪…

Python数据分析实战-Series转DataFrame并将index设为新的一列(附源码和实现效果)

实现功能 Series转DataFrame并将index设为新的一列 实现代码 import pandas as pd# 创创建series series pd.Series([1, 2, 3, 4, 5])# 创建一个DataFrame对象 data {column_name: series} df pd.DataFrame(data)# 重新设置索引&#xff0c;将原有的索引作为新的一列 df.r…

计算机的 bit(比特)和Byte(字节)

我们来说说和数据有关的单位 bit 和 Byte。 在说这这个数据当我之前&#xff0c;大家应该都知道计算机实际上只能处理0和1。 计算机能够把0和1转换为电路中的信号来进行计算&#xff0c;这个其实就是计算机的本质。 单位定义 我们先对需要使用的单位进行一些定义。 bit&…

Swift 周报 第三十六期

文章目录 前言新闻和社区消息称苹果公司和印度财政部官员磋商&#xff0c;扩大在印度的制造产能iPhone 15 Pro 机型新增泰坦灰iPhone 15 全系配 USB-C 苹果拒绝接口和安卓互通 提案正在审查的提案 Swift论坛推荐博文话题讨论关于我们 前言 本期是 Swift 编辑组整理周报的第三十…