递归(一)——用“单步调试法”来理解递归调用过程

        在算法的学习过程中,“递归”算法似乎显得很神秘,时常让学习者一头雾水,感觉莫名其妙,可是掌握递归又是一个绕不过去的坎,因为很多更高级的数据结构和算法思想就是以递归为基础的,比如数据结构中的树和图,比如算法中的动态规划。

        递归函数之所以让我们觉得很玄幻,首先是无法理解函数自己调用自己, 其次是我们不清楚函数执行过程中的每一步都发生了什么,此时此刻,多么想单步调试代码啊,可是如果这么做了,就会发现仿佛进入了一个N维空间,一层又一层啊 ... ...  简直是有去无回,进去就出不来的感觉,放弃吧!

        但今天我们就着一个简单的题目,执行一次单步调试,看看递归到底怎么执行的每一步。

        做题目之前,我们只需要知道:递归就是把大事分解成若干件小事,把每一个小事的结果求出,然后通过一个决策过程就能得到大事的答案

  • 题目

求数组arr[L...R] 中的最大值,用递归方法实现。

  • 思路:利用二分法,将组数分为左、右两个部分,找到左侧数组的最大值,再找到右侧数组的最大值,两个最大值进行比较,较大的那个就是arr的最大值。

1)将L~ R 范围分成左右两个部分,左边:L~Mid, 右边:Mid~R;

2)求左边部分的最大值Max_L, 右边部分的最大值 Max_R;

3)   L~R 范围上的最大值,就是 max{Max_L, Max_R}

递归过程发生在2),不断的利用二分法,求左右两个部分的最大值,直到左部分(或右部分)只有一个数,则结束二分,结束递归。

  • 代码实现
#include <stdio.h>

int findMax(int* arr, int L, int R){
    if(L == R ) {
        return arr[L];
    }
    int mid = L + ((R-L)>>1);
    int max_l = findMax(arr,L,mid);
    int max_r = findMax(arr,mid+1,R);
    return max_l>max_r?max_l:max_r;
}

int main(){
    int arr[4] = {3,7,4,6};
    printf("max = %d", findMax(arr,0,3));
    return 0;
}

小技巧

        在求两个数L 和 R的中值的时候,代码中没有使用 mid = (L+R)/ 2, 而是利用了位运算,并且没有直接求两个数的和 而是去求了两个数的差,这是因为,如果L 和 R的值很大的话,它们的和有可能是个超大数,超出数据类型的表示范围,造成溢出报错。

        所以,这行代码利用位运算 代替除法运算,提高了程序的运行效率,利用求差运算取代求和运算,避免了数据溢出。

        int mid = L + ((R-L)>>1);  // 两个数的中值 = 较小的那个数 + 两个数差的一半

  • 运行结果

  • 代码分析

        接下来,我们来分析每一步的递归调用都发生了什么。

数组值3746
下标0123

        递归函数也是函数,它之所以可以顺利的完成了调用过程,就是因为利用了系统栈。这就是递归能够实现的真相。下图表格就代表系统栈。

        当程序执行进入  findMax (0,3) 后,不满足 if(L== R)条件,程序继续执行,执行到第8 行时,出现了子函数的调用语句,此时就需要保护现场,将当前函数findMax(0,3),以及局部变量mid ( =1), max_L = ? 和当前中断的行号 (=8) 都存入系统栈,接着销毁findMax(0,3)的执行过程,进入子函数 find(0,1); 

f(0,3), mid = 1, max_l = ? 行号:8

进入函数findMax(0,1) 后,不满足 if(L == R) ,程序继续执行到第8行,又遇到子程序调用,再次保存现场,将当前函数findMax(0,1),以及其对应的局部变量mid = 0, max_L=? , 行号=8 存入系统栈后,然后销毁findMax(0,1)的执行过程,进入子函数findMax(0,0);

f(0,1),mid=0, max_l= ? 行号:8
f(0,3), mid = 1, max_l = ? 行号:8

        进入函数findMax(0,0) 后,满足L== R,函数返回。 返回值给系统栈的栈顶。此时的栈顶是findMax(0,1), 那么就重建函数findMax(0,1)的执行过程,等待接收返回值的是 findMax(0,1) 的max_l, 即max_l = 4, 此时的行号是8 ,就会继续往下执行第9行语句。

f(0,3), mid = 1, max_l = ? 行号:8

   

执行到第9行语句,又遇到子函数的调用,保存现场 findMax(0,1), mid=0,mar_l = 4, max_r = ? ,行号=9;销毁findMax(0,1)的执行过程,进入子函数findMax(1,1);

f(0,1),mid = 0,max_l = 4, max_r = ? ,行号: 9
f(0,3), mid = 1, max_l = ? 行号:8

进入到子函数findMax(1,1), 满足 L==R, 函数返回, 返回值给系统栈的栈顶。此时的栈顶是findMax(0,1), 那么就重建函数findMax(0,1)执行过程,等待接收返回值是 findMax(0,1) 的max_r, 即max_r = 7, 此时的行号是9 ,继续往下执行第10行语句:return max_l>max_r?max_l:max_r, 返回值为7 ,给到系统栈栈顶,此时的栈顶是findMax(0,3) ;

f(0,3), mid = 1, max_l = ? 行号:8

重新构建findMax(0,3)的执行过程,max_l = 7, 程序继续往下执行到第9行,遇到子函数调用,保存现场,当前函数是findMax(0,3), mid = 1, max_l = 7, max_r=?,行号=9, 销毁findMax(0,3)的执行过程,进入子函数findMax(2,3);

f(0,3), mid = 1, max_l = 7,  max_r = ? 行号:9

进入函数findMax(2,3) , 不满足 if(L== R), 程序继续执行到第8行,遇到子函数调用,保存现场:findMax(2,3), mid = 2,max_l = ? 行号=8 ,销毁函数findMax(2,3) 的执行过程,进入子函数findMax(2,2);

f(2,3), mid = 2,max_l = ?,行号:8
f(0,3), mid = 1, max_l = 7,  max_r = ? 行号:9

进入函数findMax(2,2), 满足条件 L==R, 函数返回。返回值给到系统栈栈顶,此时系统栈栈顶是findMax(2,3), 重新构建函数findMax(2,3) 的执行过程。 即max_l = 返回值4, 程序继续往下执行到第9行,遇到子函数调用,保存现场: f(2,3), mid = 2, max_l = 4, max_r = ? 行号9,然后销毁当前函数 findMax(2,3)的执行过程, 进入子函数findMax(3,3);

f(2,3), mid = 2,max_l = 4, max_r = ? 行号:9
f(0,3), mid = 1, max_l = 7,  max_r = ? 行号:9

进入函数findMax(3,3), 满足条件L== R, 函数返回。返回值给到系统栈栈顶,此时系统栈栈顶是findMax(2,3), 重新构建函数findMax(2,3) 的执行过程。 max_r = 返回值6, 程序继续往下执行到第10行,return max_l >max_r? max_l:max_r, 所以返回 6. 

f(0,3), mid = 1, max_l = 7,  max_r = ? 行号:9

返回值给到系统栈栈顶,此时栈顶是函数findMax(0,3) , 重新构建函数findMax(0,3) 的执行过程,max_r = 返回值6, 程序继续往下执行到第10行, return max_l >max_r? max_l:max_r, 返回7.

栈空,程序执行结束。

  • 分析总结

1. 整个分析过程虽然比较啰嗦,但我们通过“单步调试”这种方法,给递归调用做了揭秘,知道了递归函数在执行过程中到底是怎么操作的,系统栈就是它的大杀器。

2. 通过对调用过程的感性的直观认识,知道为什么“递归层数不能太深”了,因为如果递归层数太深,就会增大对系统栈的开销,有可能出现栈溢出。

3. 在用递归思想处理问题的时候,我们不可能每一次都做这样的“单步调试”的分析,也没有这样分析的必要,但是要做做到“心中有数”,大脑中是有一张脑图的👇

对程序的运行走向,也要略知一二👇:

        第一步: 求0位置~3位置上的最大值 fun(0,3);  计算出中点 mid = 1;

        第二步: 求0位置~1位置上的最大值 fun(0,1), 计算出中点 mid = 0;

        第三步: 因为L== R== 0, 所以fun(0) = 3;

        第四步: 因为L == R == 1, 所以 fun(1) = 7;

        第五步: 0位置~1位置的最大值,是7

        第六步:求2位置~3位置的最大值fun(2,3), mid = 2;

        第七步:因为 L== R == 2, 所以 fun(2) = 4;

        第八步:因为 L == R == 3,所以 fun(3) = 6;

        第九步:2位置~3位置的最大值是6;

        第十步:0位置~ 3位置的最大值是 7;

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

工商业储能柜用的Acrel-2000ES储能能量管理系统-安科瑞 蒋静

概述 Acrel-2000ES储能能量管理系统&#xff0c;专门针对工商业储能柜、储能集装箱研发的一款储能EMS&#xff0c;具有完善的储能监控与管理功能,涵盖了储能系统设备(PCS、BMS、电表、消防、空调等)的详细信息&#xff0c;实现了数据采集、数据处理、数据存储、数据查询与分析…

数据结构9——排序

一、冒泡排序 冒泡排序&#xff08;Bubble Sort&#xff09;&#xff0c;顾名思义&#xff0c;就是指越小的元素会经由交换慢慢“浮”到数列的顶端。 算法原理 从左到右&#xff0c;依次比较相邻的元素大小&#xff0c;更大的元素交换到右边&#xff1b;从第一组相邻元素比较…

Talk|北京大学PKU-DAIR余昭辰:从多模态理解到生成 - 从LLM到Diffusion Model

本期为TechBeat人工智能社区第603期线上Talk。 北京时间6月26日(周三)20:00&#xff0c;北京大学PKU-DAIR实习生—余昭辰的Talk已经准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “从多模态理解到生成 - 从LLM到Diffusion Model”&#xff0c;在本次Talk…

.Net WebApi启动 Swagger异常报错: Failed to load API definition

问题描述&#xff1a; 基于.Net6.0的WebApi 启动Swagger报错&#xff1a;Failed to load API definition。即无法加载API定义。 解决方法&#xff1a; 分析程序输出日志&#xff1a; 错误信息&#xff1a; ERROR Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMid…

无线领夹麦克风品牌排名,揭秘哪种领夹麦性价比高!

在直播电商和Vlog的热潮推动下&#xff0c;自媒体内容创作迎来了前所未有的繁荣。麦克风行业也因应这一趋势&#xff0c;迎来了快速的增长期。特别是无线领夹麦克风&#xff0c;以其便携性和高效的录音能力&#xff0c;迅速成为视频制作者的新宠。它不仅在直播带货和短视频制作…

[JS]DOM事件

事件监听 让程序检测是否有事件产生, 一旦事件触发, 就调用函数做出响应 事件三要素: 事件源(谁的事件) 事件类型(如何触发) 事件处理程序(做什么) function fn() {} // 绑定事件 btn.addEventListener(click, fnction() { })// 绑定事件 btn.addEventListener(click, fn)//…

openlayer 图层点击事件 鼠标单击

背景&#xff1a; 接上一篇博客&#xff0c;如何渲染图层&#xff0c;渲染不同颜色的图层&#xff1f; 一个图层创建好了&#xff0c;接下来我们要做的是&#xff0c;如何通过鼠标点击打开点击对象的详情弹框&#xff1f;鼠标点击的是layer图层里的featrue要素&#xff0c;这…

数字AI化银行数字化转型实战手册银行数字化转型大客户营销销售讲师培训师唐兴通谈存量客户理财金融科技与场景化

推动银行数字化转型的五个关键因素 推动银行数字化转型的五个关键因素&#xff1a; 客户体验。为客户提供便利和个性化是数字化转型的关键因素。银行应开发和实施创新的数字渠道&#xff0c;例如移动应用程序、网上银行、聊天机器人等&#xff0c;以方便获取金融服务并提高客户…

使用微信开发者工具创建运行项目全流程

小程序基础知识 1. 认识什么是小程序 什么是微信小程序 微信小程序是一种运行在微信内部的 轻量级 应用程序。 在使用小程序时 不需要下载安装&#xff0c;用户 扫一扫 或 搜一下 即可打开应用。它也体现了 “用完即走” 的理念&#xff0c;用户不用关心安装太多应用的问题…

LangChain让LLM带上记忆

最近两年&#xff0c;我们见识了“百模大战”&#xff0c;领略到了大型语言模型&#xff08;LLM&#xff09;的风采&#xff0c;但它们也存在一个显著的缺陷&#xff1a;没有记忆。 在对话中&#xff0c;无法记住上下文的 LLM 常常会让用户感到困扰。本文探讨如何利用 LangCha…

2024-6-27 石群电路-31

2024-6-27&#xff0c;星期四&#xff0c;12:52&#xff0c;天气&#xff1a;雨&#xff0c;心情&#xff1a;晴。今天没有什么事情发生&#xff0c;继续学习&#xff0c;加油&#xff01;&#xff01;&#xff01;&#xff01;&#xff01; 今日观看了石群老师电路课程的视频…

从此以后,将硬件接入大语言模型(LLM)将变得如此简单~

一、前言 本文中将使用ESP-AI开源库来实现将硬件接入AI&#xff0c;整个过程将非常的轻松~ 什么是ESP-AI? 为你的开发板提供全套的AI对话方案&#xff0c;包括但不限于 ESP32 系列开发板的 IATLLMTTS 集成方案。 交流群 QQ 交流群: 854445223 技术栈 ESP-AI 分为了服务端和…

Databend 怎么看 OpenAI 收购实时数仓 Rockset?

6月21日(上周五)&#xff0c;OpenAI 官方宣布完成对实时分析数据库 Rockset 的收购&#xff0c;一时引起数据库圈和 AI 圈热议&#xff0c;很多朋友也来询问 Databend 如何看待这个事件。这次收购表明了市场对实时数据分析和数据处理解决方案的高度重视&#xff0c;数据是 AI 发…

Win10扩充C盘(把其他盘存储空间分给C盘)

C盘虽然没有安装任何软件&#xff0c;但无奈安装某些软件&#xff08;例如VS&#xff0c;QuarC等&#xff09;总会占用C盘容量&#xff0c;且C盘内存很小&#xff08;只有60G左右&#xff09;&#xff0c;看着D盘的三四十空闲内存&#xff0c;决定把D盘内存分给C盘30G&#xff…

C++入门 list的模拟实现

目录 list的节点类 list的迭代器类 list的模拟实现 要模拟实现list&#xff0c;必须要熟悉list的底层结构以及其接口的含义&#xff0c;通过之前学习&#xff0c;这些内容已基本掌握&#xff0c;现在我们来模拟实现list。 参照带头双向循环链表的结构&#xff0c;我们可以建…

ConvMixer 论文与代码解析

paper&#xff1a;Patches Are All You Need? official implementation&#xff1a;https://github.com/locuslab/convmixer 精度上去了&#xff0c;推理速度只有卷积和ViTs的四分之一&#xff01; 出发点 文章讨论了卷积神经网络&#xff08;CNN&#xff09;在视觉任务中…

#### 广告投放 ####

以巨量引擎为例&#xff1a; 计费模式 eCPM&#xff08;expected Cost Per Mile&#xff0c;估计千次展示收入&#xff09; 概括&#xff1a; ecpm为千次展示的预估收益&#xff0c;是广告平台用来给广告排序的指标。 注意是展示而不是千次点击收益&#xff0c;展示了可能不…

从0到1:亮数据浏览器,为数据采集工作注入全新动力

亮数据浏览器提升数据采集效率 一、 导言1.1 引入亮数据浏览器的重要性1.2 简要介绍本文将涉及的主题和内容 二、 亮数据浏览器简介2.1. 什么是亮数据浏览器2.2. 亮数据浏览器的特点和优势 三、优化数据采集的核心功能3.1 自动化数据采集3.1.1 通过亮数据浏览器实现自动化数据采…

LangChain入门之 GPT 和小范大人不太熟?

前言 嗨&#xff0c;大家好&#xff01;我是海鸽。 《庆余年2》刚刚完结&#xff0c;热度不减&#xff0c;我忍不住好奇&#xff1a;我们的AI伙伴GPT&#xff0c;是否也对剧中那位机智过人的小范大人有所耳闻&#xff1f; 不仅如此&#xff0c;最近我们还尝试了LangChain的调…

Xcode安装Simulator失败问题解决方法

Xcode安装Simulator_Runtime失败&#xff0c;安装包离线安装保姆级教程 Xcode更新之后有时候会提示要安装模拟器运行时环境&#xff0c;但是用Xcode更新会因为网络原因&#xff0c;我觉得基本上就是因为苹果服务器的连接不稳定导致的&#xff0c;更可气的是不支持断点续…