0617,递归问题(详细——好好好一入递归深似海)

 目录

第七章(函数)思维导图
总结:递归三问

01,电影院问题

理解递归的执行过程

02,FIBNACCI数列

不是说具有递归结构的问题,就可以用递归求解——存在大量的重复计算

 法一:自顶向下求解

BUG:

法一总结:

法二:自底向上求解

法二总结:

03,汉诺塔问题

递归的表达能力

04,约瑟夫环(简单版)

有些问题的递归结构很难发现,如果找到了这个问题的递归结构,对这个问题的理解就更深了,往往可以找到更好的求解方式

方法一:循环链表

方法二:递归公式

作业01:(汉诺塔)

解答:

作业02:  约瑟夫环(完整版)

答案:

作业03:(MAX,SEC_MAX)

错误代码01/不行的哦:

解法2:

答案:

作业04:(秒数转换):

 解答:

答案:

recursion——re/重复——cur/走,流动——sion/名词后缀
走重复的路

总结:递归三问

递归公式:
想不明白就从边界条件的下一层抽象出来
根据定义,大问题小问题的求解方式都是一样的,只是数据规模不一样
只考虑这一层和上一层(假定上一层已经求解)

思考的时候不要陷入细节,从问题的模式考虑?

01,电影院问题

理解递归的执行过程

乌漆嘛黑,你和你女朋友在第几排——大问题
”哥们,你在第几排“——递——子问题(求解方式和大问题一致,只是数据规模不一致)
”前面有鬼,第一排“——第二排——第三排——归——子问题的解合并成大问题的解

02,FIBNACCI数列

不是说具有递归结构的问题,就可以用递归求解——存在大量的重复计算

 法一:自顶向下求解

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>long long fib(int n) {if (n == 1 || n == 2) {return 1;}return fib(n - 1) + fib(n - 2);
}int main(void) {int n;do {scanf("%d", &n);printf("%lld\n", fib(n));} while (n != 0);return 0;
}
BUG:
  1. 递归深度和堆栈溢出问题:

    • 当输入的 n 值较大时,例如 n 达到 40 或更高,递归调用 fib(n) 的深度会非常大,可能导致堆栈溢出。这是因为递归方式的 Fibonacci 计算会在堆栈中不断增加帧,直到超出系统允许的最大深度。
  2. 递归终止条件:

    • 当 n 等于 0 时,你的程序会继续运行并输出 fib(0) 的值。Fibonacci 序列的定义中通常认为 fib(0) 是 0,而不是 1。因此,你需要考虑在 fib 函数中处理 n == 0 的情况
法一总结:

不能在有限的时间内得到正确的结果

递归树——有大量的重复结点——重复的树——大量重复的计算

思考方式——自顶向下

法二:自底向上求解

自底向上求解

动态规划——算法设计思想,可以将指数级别的算法,优化成多项式级别的思想,——避免重复计算问题

假定!上一个问题已经求解
0,1,
2,3,5……

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>long long fib2(int n) {if (n == 0 || n == 1) {return 1;}int i;long long a = 1;long long b = 1;long long t;for (i = 2; i <= n; i++) {     //2的时候进入循环t = a + b;               //循环不变式——每次进入循环体之前都成立的条件a = b;                   //fib(i)未求解,fib(2)未求解,fib(n+1)未求解b = t;                                              //循环退出点————i=n+1 }                                                      //循环不变式可以保持到循环的结束return t;
}int main(void) {int n;do {scanf("%d", &n);printf("%lld\n", fib2(n));} while (n != 0);return 0;
}
法二总结:

思考方式——自底向上

循环不变式——每次进入循环体之前都成立的条件,可以保持到循环的结束

03,汉诺塔问题

递归的表达能力

printf("Total step(s): %lld\n", (1LL << n) - 1);
根据汉诺塔问题的特性,当有n个盘子时,移动次数等于2^n - 1。在这里,(1LL << n) - 1 就是利用位运算得到移动的总步数 

实在想不懂,从N=2的情况归纳递归表达式

1 A-B,将N-1个 盘子从目标柱子——》辅助柱子     
2 A-C,将N   个 盘子从源柱子   ——》目标柱子
1 B-C,将N-1个 盘子从辅助柱子——》目标柱子

//说的都是前一个,假定前一个问题已经解决,即假定已经完成N-2个盘子移到目标柱子

#include <stdio.h>void move(int n, char source, char target, char auxiliary) {     //源,目标,辅助柱子if (n == 1) {printf("Move disk 1 from %c to %c\n", source, target);   //退出点  1——A-Creturn;}move(n - 1, source, auxiliary, target);      //把N-1个盘子  从 源柱子 移动到 辅助柱子       move(n-1)次printf("Move disk %d from %c to %c\n", n, source, target);      //把N盘子 ,从源柱子移动到 目标柱子  1次move(n - 1, auxiliary, target, source);     //把N-1个  从 辅助柱子 移动到 目标柱子       move(n-1)次
}int main() {int n;// 汉诺塔的盘子数量___BUGdo {scanf_s("%d", &n);if (n == 0) {break;}move(n, 'A', 'C', 'B');printf("Total step(s): %lld\n", (1LL << n) - 1);//move(n-1)*2-1次} while (1);return 0;
}//所以,总步数(T(k + 1)) 为:
//[T(k + 1) = T(k) + 1 + T(k) = 2T(k) + 1]
//
//根据归纳假设(T(k) = 2 ^ k - 1),代入上式:
//[T(k + 1) = 2(2 ^ k - 1) + 1 = 2 ^ {k + 1} - 2 + 1 = 2 ^ {k + 1} - 1]
//
//因此,(T(k + 1) = 2 ^ {k + 1} - 1) 成立。

 

04,约瑟夫环(简单版)

有些问题的递归结构很难发现,如果找到了这个问题的递归结构,对这个问题的理解就更深了,往往可以找到更好的求解方式

方法一:循环链表

时间复杂度——2*2(n-1)——O(N)
空间复杂度——O(n)

方法二:递归公式

边界条件:只有一个的人的时候,return 1;剩下两个人的时候,刀掉第二个,return 1
递归公式思考:

当人数为偶数时      
1,2,3,4,5,6,7,8,9,10,11,12
1,--,3,--,5,--,7,--,9,--,11,--          第一轮🔪掉所有的偶数,刚好又从1开始
1,--,2,--,3,--,4,--,5,--,6,--            重新编号继续刀人
1,--,--,--,3,--,--,--,5,--,--,--,       第二轮:刀掉所有重新编号的偶数
递——重新编号,一直刀刀剩下最后一个人
归——最后一个人,最后的编号,逐级返回(找到和上一级编号的关系),得到真正的编号
return x=6-->11        f(x)=2x-1

当人数为奇数时 
1,2,3,4,5,6,7,8,9,10,11       第一轮,刀掉所有的偶数
1,--,3,--,5,--,7,--,9,--,11       重新编号继续刀人。PS:新的编号,应该从11开始
2,--,3,--,4,--,5,--,6,--,1        
0,--,1,--,2,--,3,--,4,--,5         或者编号改一下?
--,--,1,--,--,--,3,--,--,--,5        继续刀掉偶数
归——真正的编号x=9,返回的编号x=4    
return x=4-->9     f(x)=2x+1                     要搞清楚编号传递的先后顺序,然后找个好带的推

n为偶数   joseph(n)=2*joseph(n/2)-1
n为奇数   joseph(n)=2*joseph(n/2)+1

时间复杂度:O(logN)
空间复杂度:O(logN)

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>int joseph(int n) {if (n == 1 || n == 2) {return 1;}if (n & 0x1) {  //奇数1return (joseph(n >> 1) << 1) + 1;}else {return (joseph(n >> 1) << 1 )+ 1;}
}int main(void) {printf("joseph(5)=%d\n", joseph(5));printf("joseph(8)=%d\n", joseph(8));
}/*
约瑟夫环:n 个人站成一圈,每 m 个人处决一个人。
假设这 n 个人的编号为 1, 2, ..., n,并且从 1 开始数,问最终活下来的人编号是多少? (拓展题)
int joseph(int n, int m);*/

作业01:(汉诺塔)

有三根杆子A,B,C。A杆上有 N 个 (N>1) 穿孔圆盘,盘的尺寸由下到上依次变小。要求按下列规则将所有圆盘移至 C 杆:

1. 每次只能移动一个圆盘;
2. 大盘不能叠在小盘上面。

提示:可将圆盘临时置于 B 杆,也可将从 A 杆移出的圆盘重新移回 A 杆,但都必须遵循上述两条规则。

问:最少需要移动多少次?如何移?

解答:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>int move(int n,char origin,char middle,char target) {if (n == 1) {printf("move %d :%c->%c\n",n, origin, target);}//1 A-B//2 A-C//1 B-Cmove(n - 1, origin, middle,target);printf("move %d :%c->%c\n", n-1,origin, target);move(n, middle, target, origin);
}int main(void) {int n;do {scanf("%d", &n);move(n,'A','B','C');printf("number of times: %d\n", n << 1 - 1);} while (n!= 0);return 0;
}

作业02:  约瑟夫环(完整版)

约瑟夫环:n 个人站成一圈,每 m 个人处决一个人。假设这 n 个人的编号为 1, 2, ..., n,并且从 1 开始数,问最终活下来的人编号是多少? (拓展题)
int joseph(int n, int m);

答案:

1,2,3,4,5,6,7       n=7,m=3       结果是4

1,2,--,4,5,6,7       刀掉3,重新编号
5,6,--,1,2,3,4        重新编号之后,新/源 编号的关系(1+m)%n --(1+3)%7=4   

4,5,--,0,1,2,3       存在特例,(4+3)%7==0改从0开始,(3+3)%m+1=7

递归公式: f(x)=(x+m)%n+1;

边界条件:当只剩下一个人的时候,编号为0-x(从0开始编号)       

return (joseph_helper(n - 1, m) + m) % n;

找到小解和大解之间的关系,直接套????

// 循环链表:空间复杂度O(n), 时间复杂度:O(mn)
//    递归: 空间复杂度O(n), 时间复杂度:O(n)#include <stdio.h>int joseph_helper(int n, int m) {// 从0开始编号// 边界条件if (n == 1) return 0;return (joseph_helper(n - 1, m) + m) % n;
}int joseph(int n, int m) {// 从1开始编号 // 委托return joseph_helper(n, m) + 1;
}int main(void) {printf("joseph(7, 3) = %d\n", joseph(7, 3));return 0;
}

作业03:(MAX,SEC_MAX)

查找数组中最大的元素和第二大的元素,并将它们分别存放在由 largest 和 second_largest 指向的变量中。

void find_two_largest(int arr[], int n, int* largest, int* second_largest);

注意:为了简化程序,数组不存在重复元素。

错误代码01/不行的哦:

基础不牢地动山摇
定义了两个野指针  指针定义:int *p=&a,int *q=p;     ……还有什么好说的,丢人

需要的数据和数据类型INT,所以先创建变量INT
1,定义了野指针,数据没有载体
2,将指针指向数组第一个元素的话,会改变数组的值,不能完整的循环

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#define SIZE(a) (sizeof(a)/sizeof(a[0]))int* largest;
int* second_largest;void find_two_largest(int arr[], int n, int* largest, int* second_largest) {int i; int j;for (i = 0; i < n; i++) {for (j = 0; j <n-i ; j++) {if (arr[i] > arr[j]) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}}}*largest = arr[n - 1];*second_largest = arr[n - 2];
}int main(void) {int arr[] = { 3,5,343,2,4,6,232,2,3,0 };find_two_largest(arr, SIZE(arr), &largest, &second_largest);printf("largest %d,second_largest %d\n", *largest ,*second_largest);return 0;
}

解法2:

好好好,倒也不必指针了,写到第四题就清醒了

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#define SIZE(a) (sizeof(a)/sizeof(a[0]))void find_two_largest(int arr[], int n, int* largest, int* second_largest) {if (arr[0] > arr[1]) {*largest = arr[0];*second_largest = arr[1];}else {*largest = arr[1];*second_largest = arr[0];}int i;for (i = 2; i < n; i++) {if (arr[i] > *largest) {int temp = *largest;*largest = arr[i];*second_largest = temp;}else if (arr[i] > *second_largest && arr[i] < *largest) {*second_largest = arr[i];}}
}int main(void) {int arr[] = { 3,5,343,2,4,6,232,2,3,0 };int max = 0; int sec = 0;//int* largest=&max;//野指针//int* second_largest =&sec;find_two_largest(arr, SIZE(arr), &max, &sec);  printf("largest %d,second_largest %d\n",max ,sec);return 0;
}

答案:

ELSE-IF逻辑的必要性,和正确性,减少比较的次数

int main(void) {int largest, second_largest;int arr[] = {9, 5, 2, 7, 1, 3, 4, 6, 8, 0};find_two_largest(arr, 10, &largest, &second_largest);printf("largest = %d, second_largest = %d\n", largest, second_largest);return 0;
}void find_two_largest(int arr[], int n, int* largest, int* second_largest) {*largest = arr[0] >= arr[1] ? arr[0] : arr[1];*second_largest = arr[0] < arr[1] ? arr[0] : arr[1];for (int i = 2; i < n; i++) {if (arr[i] > *largest) {*second_largest = *largest;*largest = arr[i];} else if (arr[i] > *second_largest) {*second_largest = arr[i];}}
}

作业04:(秒数转换):

void split_time(long total_sec, int* hour, int* minute, int* second);

total_sec 表示从午夜12:00:00开始计算的秒数。请将 total_sec 转化以时(0-23)、分(0-59)、秒(0-59)表示的时间,并存放到函数外部由指针 hour, minute, second 指向的变量中。并在外部,打印出当前的时间

 解答:

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>void split_time(long total_sec, int* hour, int* minute, int* second) {*hour = total_sec / (60 * 60);*minute = total_sec/60;*second = total_sec % 60;while (*hour >= 23) {*hour %= 24;}while (*minute >= 59) {*minute %= 60;}
}
int main(void) {long total_sec;do{int t_hour, t_minute, t_second;scanf("%ld", &total_sec);split_time(total_sec, &t_hour, &t_minute, &t_second);printf("%-4.2d:%-4.2d:%-4.2d\n", t_hour, t_minute, t_second);} while (total_sec != 0);return 0;
}/*
void split_time(long total_sec, int* hour, int* minute, int* second);
total_sec 表示从午夜12:00:00开始计算的秒数。请将 total_sec 转化以时(0-23)、分(0-59)、秒(0-59)表示的时间,
并存放到函数外部由指针 hour, minute, second 指向的变量中。并在外部,打印出当前的时间*/

答案:

*second = total_sec % 60;
*minute = (total_sec / 60) % 60;
*hour = (total_sec / 60 / 60) % 24;
int main(void) {long total_sec = 9527;int hour, minute, second;// 指针可以做为返回值来用split_time(total_sec, &hour, &minute, &second);printf("%d:%d:%d\n", hour, minute, second);return 0;
}void split_time(long total_sec, int* hour, int* minute, int* second) {*second = total_sec % 60;*minute = (total_sec / 60) % 60;*hour = (total_sec / 60 / 60) % 24;
}

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

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

相关文章

ruoyi框架第二天,自定义接口,在若依框架显示数据

书接上文&#xff0c;搭建好若依&#xff0c;并且创建自己想要的模块。 让ruoyi框架显示自己想要的模块。 今天&#xff0c;我们就要自定义接口&#xff0c;模仿ruoyi框架收发数据模式&#xff0c;来创建自己的模块。 我们创建好自己想要的接口&#xff0c;我这个是无参的查…

【Java】已解决java.util.EmptyStackException异常

文章目录 一、问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决java.util.EmptyStackException异常 一、问题背景 java.util.EmptyStackException是Java在使用java.util.Stack类时可能会遇到的一个异常。这个异常通常在尝试从空的栈中弹出&am…

四连杆机构运动学仿真 | Matlab源码+理论文本【超详细】

【程序简介】&#x1f4bb;&#x1f50d; 本程序通过matlab实现了四连杆机构的运动学仿真编程&#xff0c;动态展现了四连杆机构的角位移、角速度和角加速度的时程曲线&#xff0c;除了程序本身&#xff0c;还提供了机构运动学详细的公式推导文档&#xff0c;从而帮助大家更好…

2024年化学、能源与核工程国际会议(ICCENE 2024)

2024年化学、能源与核工程国际会议(ICCENE 2024) 2024 International Conference on Chemical, Energy and Nuclear Engineering (ICCENE 2024) 会议地点&#xff1a;三亚&#xff0c;中国 网址&#xff1a;www.iccene.com 邮箱: iccenesub-conf.com 投稿主题请注明:ICCEN…

【面试题】Spring常见面试题整理2024(全是干货!!!)

备战实习&#xff0c;会定期给大家整理常考的面试题&#xff0c;大家一起加油&#xff01; &#x1f3af; 注意&#xff1a;文章若有错误的地方&#xff0c;欢迎评论区里面指正 &#x1f36d; 系列文章目录 【面试题】MySQL常见面试题总结【面试题】面试题分享之JVM篇【面试题…

信息学奥赛初赛天天练-29-CSP-J2022阅读程序-掌握递归、递推、动态规划、二分与极值函数应用

PDF文档公众号回复关键字:20240619 2022 CSP-J 阅读程序2 阅读程序(判断题1.5分 选择题3分 共计40分 ) 01 #include <algorithm> 02 #include <iostream> 03 #include <limits> 04 05 using namespace std; 06 07 const int MAXN 105; 08 const int MAX…

C/C++ string模拟实现

1.模拟准备 1.1因为是模拟string&#xff0c;防止与库发生冲突&#xff0c;所以需要命名空间namespace隔离一下&#xff0c;我们来看一下基本内容 namespace yx {class string{private://char _buff[16]; lunix下小于16字节就存buff里char* _str;size_t _size;size_t _capac…

2713. 矩阵中严格递增的单元格数

题目 给定一个 m x n 的整数矩阵 mat&#xff0c;我们需要找出从某个单元格出发可以访问的最大单元格数量。移动规则是可以从当前单元格移动到同一行或同一列的任何其他单元格&#xff0c;但目标单元格的值必须严格大于当前单元格的值。需要返回最大可访问的单元格数量。 示例…

服务器流量收发测试

文章目录 一、概述二、实现方式一&#xff1a;编码1. 主要流程2. 核心代码3. 布署 三、实现方式二&#xff1a;脚本1.脚本编写2. 新增crontab任务 四、查看结果 一、概述 我们在安装vnStat、wondershaper便想通过实际的数据收发来进行测试。 二、实现方式一&#xff1a;编码 …

C#客户端

控件 打开链接 Socket socket; // 打开连接 private void button1_Click(object sender, EventArgs e) {button1.Enabled false;button2.Enabled true;//1 创建socket客户端对象socket new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);// 2…

ChatGPT 提示词技巧一本速通

目录 一、基本术语 二、提示词设计的基本原则 三、书写技巧 2.1 赋予角色 2.2 使用分隔符 2.2 结构化输出 2.3 指定步骤 2.4 提供示例 2.5 指定长度 2.6 使用或引用参考文本 2.7 提示模型进行自我判断 2.8 思考问题的解决过程 ​编辑 2.10 询问是否有遗漏 2.11 …

Studio One 6中文官网下载-Studio One软件下载-音乐编曲软件下载安装

​众多使用者向我们证明了轨道预设保存并调用您喜欢的曲目设置的快照&#xff0c;将您的工作流程效率升级到最新水平&#xff01;存储指定轨道或频道的每项设置&#xff0c;以便即时调用&#xff0c;即使对于多个选定的曲目/频道也同样支持&#xff0c;轨道预设可存储轨道延迟、…

如何将本地代码上传到git上面

精简步骤&#xff1a; git init git add . git commit -m "first init" git remote add origin 远程仓库地址 git push -u origin master 目录 1、初始化本地仓库 2、添加所有文件到本地仓库 3、提交更改到本地仓库 4、添加github仓库作为远程仓库 5、推送更改…

国际期货投机交易的常见操作方法:

一、在开仓阶段&#xff0c;入市时机的选择&#xff1a; &#xff08;1&#xff09;通过基本分析法&#xff0c;判断市场处于牛市还是熊市 开仓阶段&#xff0c;入市时机的选择&#xff1a;当需求增加、供给减少&#xff0c;此时价格上升&#xff0c;买入期货合约&#xff1b…

# 消息中间件 RocketMQ 高级功能和源码分析(五)

消息中间件 RocketMQ 高级功能和源码分析&#xff08;五&#xff09; 一、 消息中间件 RocketMQ 源码分析&#xff1a;NameServer 路由元数据 1、消息中间件 RocketMQ 中&#xff0c;NameServer 路由管理 NameServer 的主要作用是为消息的生产者和消息消费者提供关于主题 To…

一个小的画布Canvas页面,记录点的轨迹

Hello大家好&#xff0c;好久没有更新了&#xff0c;最近在忙一些其他的事&#xff0c;今天说一下画布canvas&#xff0c;下面是我的代码&#xff0c;实现了一个点从画布的&#xff08;0,0&#xff09;到&#xff08;canvas.width&#xff0c;canvas.height&#xff09;的一个实…

60.指针数组和数组指针

一.指针数组 指针数组是一个数组&#xff0c;在指针数组中存放的是指针变量。 定义一个指针数组p int *p[5]; 内存模型如下&#xff1a; 指针数组的初始化 #include <stdio.h>int main(void) {int a1;int b2;int c3;int i;int *p[3] {&a,&b,&c};for(i0…

椭圆的标准方程与协方差矩阵的特征值和特征向量的关系

椭圆的标准方程与协方差矩阵的特征值和特征向量的关系 flyfish 单位圆 &#xff1a;单位圆表示在标准正交基下的分布。 椭圆 &#xff1a;通过协方差矩阵的特征向量和特征值变换得到的椭圆&#xff0c;表示数据在新的坐标系下的分布。 特征向量 &#xff1a;红色箭头表示特征…

Android sensor列表和访问记录

命令: dumpsys sensorservice 1.dumpsys sensorservice查看最近申请记录 dumpsys sensorservice命令输出Previous Registrations. Previous Registrations: 23:07:43 0x00000008 pid16587 uid10397 packagecom.start.testdemo.ui.udfp.fql.XsqFQLActivity samplingPeriod66…

07.MyBatis映射器:一对一关联查询

大家好&#xff0c;我是王有志&#xff0c;一个分享硬核 Java 技术的金融摸鱼侠&#xff0c;欢迎大家加入 Java 人自己的交流群“共同富裕的 Java 人”。 《MyBatis 映射器&#xff1a;实现简单的 SQL 语句》中&#xff0c;我们在 MyBatis 映射器的查询语句中使用 resultType 元…