王道机试C++第8章递归与分治 Day35和蓝桥杯两道真题程序

2024-03-17 17:44

本文主要是介绍王道机试C++第8章递归与分治 Day35和蓝桥杯两道真题程序,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

第 8 章 递归与分治

递归是指:函数直接或间接调用自身的一种方法,通常可把一个复杂的大型问题层层转化为与原问题相似但规模较小的问题来求解。
递归策略只需少量的程序就可描述解题过程所需的多次重复计算,因此大大减少了程序的代码量。

8.1 递归策略

构成递归需要具备两个条件:
1) 子问题必须与原始问题相同,且规模更小。
2) 不能无限制地调用本身,必须有一个递归出口。

例:n 的阶乘

题目描述:
输入一个整数 n ,输出 n 的阶乘(每组测试用例可能包含多组数据,请注意处理)。
输入: 一个整数 n 1 <=   n <=   20
输出: n 的阶乘
样例输入:
3
样例输出:
6
递归问题思路:
代码表示:
#include <bits/stdc++.h>
using namespace std;int Factorial(int n){if(n==1){return 1;}else{return Factorial(n-1)*n;}
}
int main() {int n;scanf("%d",&n);printf("%d\n", Factorial(n));return 0;
}

例:汉诺塔 III

题目描述:
在一块铜板上有三根杆,最左边的杆自上而下、由小到大顺序串着由 64 个圆盘构成的塔。目的是将最左边杆上的圆盘全部移到右边的杆上条件是一次只能移动一个圆盘,并且不允许大圆盘放在小圆盘的上面。
现在我们改变这个游戏的玩法:不允许直接从最左(右)边移动到最右(左)边(每次移动一定是移到中间杆或从中间杆移出),也不允许大圆盘放到小圆盘的上面。 现在有 N 个圆盘,她至少需要多少次移动才能把这些圆盘从最左边移到最右边?
输入: 包含多组数据,每次输入一个 N 值( 1 <=   N <=  35 )。
输出: 对于每组数据,输出移动最小的次数。
样例输入:
1
3
12
样例输出:
2
26
531440
代码表示:
#include <bits/stdc++.h>
using namespace std;
long long hanoi(int n){if(n==1){return 2;}else{return 3*hanoi(n-1)+2;}
}
int main() {int n;while (scanf("%d",&n)!=EOF){printf("%lld",hanoi(n));}return 0;
}

思路提示:

       若从第一根杆上移动 N 个圆盘到第三根杆上需要 F[N] 次移动,那么综上所述 F[N] 的组成方式 如下:先移动 N -1个圆盘到第三根杆上需要 F[N -1] 次移动,然后将最大的圆盘移动到中间杆上需要 1 次移动,再将 N -1个圆盘移动回第一根杆上同样需要 F[N-1] 次移动,移动最大的盘子到第三根杆上需要 1 次移动,最后将 N -1个圆盘移动到第三根杆上需要 F[N -1] 次移动,于是便有了 F[N] = 3F[N -1] + 2 。也就是说,从第一根杆上移动 N 个圆盘到第三根杆上,需要三次从第一根杆上移动 N -1个圆盘到第三根杆上,外加两次对最大圆盘的移动。这样就可以将移动 N 个圆盘的问题转换为问题相同但规模更小的移动 N -1个圆盘的问题。

        递归出口:当 N 1 时,即从第一根杆上移动一个盘子到第三根杆上,所需的移动次数显而易见为 2。移动一个盘子所需次数是最底层的子问题,该子问题不可继续分解且易于求解。这便是递归的出口。


8.2 分治法

分治法字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多个子问题,子问题之间互相独立且与原问题相同或相似。之后再把子问题分成更小的子问题,以此类推,直到最后的子问题可以简单地直接求解,原问题的解即子问题的解的合并。由于分治法产生的子问题往往与原问题相同且模式更小,这就为使用递归策略求解提供了条件。在这种情况下,通过反复利用分治手段,可以使子问题的规模不断缩小,最终缩小到可以直接求解的情况。在从过程中会导致了递归过程的产生。分治与递归就像一对孪生兄弟,经常同时应用在算法设计中,这也是将分治法和递归策略放在同一章中进行讲解的原因。分治法的步骤如下:
1. :将问题分解为规模更小的子问题。
2. :将这些规模更小的子问题逐个击破。
3. :将已解决的子问题合并,最终得出“母”问题的解

例:Fibonacci(上交大复试)

题目描述

斐波那契数列{0,1,1,2,3,5,8,13,21,34,55...}由递归定义: F0=0 F1=1 Fn=Fn-1+Fn-2,n>=2 编写一个程序来计算斐波那契数列。

输入描述:每个情况都包含一个数字 n,您应该计算 Fn.(0<=n<=30) 。

输出描述:对于每种情况,在单独的行上打印一个数字 Fn,这意味着第 n 个斐波那契数列.

输入:1

输出: 1
代码表示
#include <bits/stdc++.h>
using namespace std;
int feibo(int n){if(n==0){return 0;}else if(n==1){return 1;}else{return feibo(n-1)+feibo(n-2);}
}
int main() {int n;while (scanf("%d",&n)!=EOF){printf("%d",feibo(n));}return 0;
}

例:二叉树(北大上机)

题目描述:
如下所示,由正整数 1,2,3, …组成了一棵特殊二叉树。我们已知这棵二叉树的最后一个结点是 n 。 现在的问题是,结点 m 所在的子树中一共包括多少个结点。比如, n = 12 m = 3 ,那么上图中的结点 13, 14, 15 及后面的结点都是不存在的,结点 m 所在子树中包括的结点有 3, 6, 7, 12 ,因此结点 m 所在的子树中共有 4 个结点。
输入: 输入数据有多行,每行给出一组测试数据,包括两个整数 m n 1 <=   m <= n <=   1000000000 )。
输出: 对于每组测试数据,输出一行,该行包含一个整数,给出结点 m 所在子树中包括的结点的数目。
样例输入:
3 12
样例输出:
4
代码表示:
#include <bits/stdc++.h>
using namespace std;
int tree(int m,int n){if(m>n){return 0;//边界情况 }else{return 1+tree(2*m,n)+tree(2*m+1,n);}
}
int main() {int m,n;while (scanf("%d%d",&m,&n)!=EOF){if(m==0){break;}printf("%d",tree(m,n));}return 0;
}

[蓝桥杯 2015 省 B] 移动距离

题目描述

X 星球居民小区的楼房全是一样的,并且按矩阵样式排列。其楼房的编号为1,2,3,⋯ 。当排满一行时,从下一行相邻的楼往反方向排号。

比如:当小区排号宽度为 6 时,开始情形如下:

1  2  3  4  5  6
12 11 10 9  8  7
13 14 15 .....

我们的问题是:已知了两个楼号 m 和 n,需要求出它们之间的最短移动距离。(不能斜线方向移动)

输入格式

输入为 3 个整数w,m,n,空格分开,都在 1到 10000 范围内。w 为排号宽度,m,n 为待计算的楼号。

输出格式

要求输出一个整数,表示 m 与 n 两楼间最短移动距离

代码表示
#include <bits/stdc++.h>
using namespace std;
int w,m,n,kx,ky,x=0,y=1;//m坐标x,y;n坐标kx,ky 
int main()
{scanf("%d%d%d",&w,&m,&n);if(n>m) swap(n,m);for(register int i=1;i<=m;i++)//枚举从1到m所有数的坐标{if(y%2==1)//正方向{x++;if(x>w) x=w,y++;}else    //反方向{x--;if(x<1) x=1,y++;}if(i==n) kx=x,ky=y;    //记录n的坐标}printf("%d",abs(kx-x)+abs(ky-y));return 0;
}
心得体会:

这个代码很巧妙,实际上所列出的数实际是什么根本就不重要,重要的是这个点所在坐标位置数的大小。以此来求解两坐标的距离。abs(kx - x)用于计算目标位置和当前位置在水平方向上的距离差的绝对值。


[蓝桥杯 2017 省 B] 日期问题

题目描述

小明正在整理一批历史文献。这些历史文献中出现了很多日期。小明知道这些日期都在 1960 年 1 月 1 日至 2059 年 12 月 31 日。令小明头疼的是,这些日期采用的格式非常不统一,有采用年/月/日的,有采用月/日/年的,还有采用日/月/年的。更加麻烦的是,年份也都省略了前两位,使得文献上的一个日期,存在很多可能的日期与其对应。比如 02/03/04,可能是 2002 年 03 月 04 日、2004 年 02 月 03 日或 2004 年 03 月 02 日。给出一个文献上的日期,你能帮助小明判断有哪些可能的日期对其对应吗?

输入格式

一个日期,格式是 AA/BB/CC。(0≤A,B,C≤9)

输出格式

输出若干个不相同的日期,每个日期一行,格式是 yyyy-MM-dd。多个日期按从早到晚排列。

#include <bits/stdc++.h>
using namespace std;char a[20];//存储输入的日期 
int M[20]={0,31,28,31,30,31,30,31,31,30,31,30,31};
int main()
{int l,i,j,k,x,y,z;scanf("%s",a);l=strlen(a);//解析日期中的年份,日期,月份为整数 x=(a[0]-48)*10+a[1]-48;y=(a[3]-48)*10+a[4]-48; z=(a[6]-48)*10+a[7]-48;for(i=1960;i<=2059;i++){if(i%400==0||(i%4==0&&i%100!=0))	//判断闰年M[2]=29;//将二月的天数设为29 for(j=1;j<=12;j++)//遍历每个月 {for(k=1;k<=M[j];k++)//遍历每个月的每一天 
//这个i%100也就是遍历到某个四位数的年份取其后两位 if((x==i%100&&y==j&&z==k)||(z==i%100&&x==j&&y==k)||(z==i%100&&y==j&&x==k)){cout<<i<<"-";if(j<10)cout<<0;cout<<j<<"-";if(k<10)cout<<0;cout<<k<<endl;}	}M[2]=28;//别忘了 }
}
心得体会:

1、在ASCII编码中,数字字符'0'的ASCII码是48,而其后的字符依次递增。因此,通过将字符'a[0]'减去48,可以得到对应的数字值。在这段代码中,a[0]是输入日期字符串的第一个字符,例如'0'、'1'、'2'等。由于这些字符是ASCII码表中的字符表示形式,而我们需要得到的是其对应的数字值,所以需要将字符'0'的ASCII码值48减去。然后,将结果乘以10,以便处理两位数的年份。接下来,从字符'a[1]'中减去48,再将结果与之前的乘积相加,从而得到两位数的年份值。

2、这道题开始处解析日期的时候就很灵活,后续采用暴力循环,接着写if语句来取不同日期的值。

这篇关于王道机试C++第8章递归与分治 Day35和蓝桥杯两道真题程序的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/819674

相关文章

从入门到精通C++11 <chrono> 库特性

《从入门到精通C++11<chrono>库特性》chrono库是C++11中一个非常强大和实用的库,它为时间处理提供了丰富的功能和类型安全的接口,通过本文的介绍,我们了解了chrono库的基本概念... 目录一、引言1.1 为什么需要<chrono>库1.2<chrono>库的基本概念二、时间段(Durat

C++20管道运算符的实现示例

《C++20管道运算符的实现示例》本文简要介绍C++20管道运算符的使用与实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录标准库的管道运算符使用自己实现类似的管道运算符我们不打算介绍太多,因为它实际属于c++20最为重要的

Visual Studio 2022 编译C++20代码的图文步骤

《VisualStudio2022编译C++20代码的图文步骤》在VisualStudio中启用C++20import功能,需设置语言标准为ISOC++20,开启扫描源查找模块依赖及实验性标... 默认创建Visual Studio桌面控制台项目代码包含C++20的import方法。右键项目的属性:

c++中的set容器介绍及操作大全

《c++中的set容器介绍及操作大全》:本文主要介绍c++中的set容器介绍及操作大全,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录​​一、核心特性​​️ ​​二、基本操作​​​​1. 初始化与赋值​​​​2. 增删查操作​​​​3. 遍历方

解析C++11 static_assert及与Boost库的关联从入门到精通

《解析C++11static_assert及与Boost库的关联从入门到精通》static_assert是C++中强大的编译时验证工具,它能够在编译阶段拦截不符合预期的类型或值,增强代码的健壮性,通... 目录一、背景知识:传统断言方法的局限性1.1 assert宏1.2 #error指令1.3 第三方解决

C++11委托构造函数和继承构造函数的实现

《C++11委托构造函数和继承构造函数的实现》C++引入了委托构造函数和继承构造函数这两个重要的特性,本文主要介绍了C++11委托构造函数和继承构造函数的实现,具有一定的参考价值,感兴趣的可以了解一下... 目录引言一、委托构造函数1.1 委托构造函数的定义与作用1.2 委托构造函数的语法1.3 委托构造函

C++11作用域枚举(Scoped Enums)的实现示例

《C++11作用域枚举(ScopedEnums)的实现示例》枚举类型是一种非常实用的工具,C++11标准引入了作用域枚举,也称为强类型枚举,本文主要介绍了C++11作用域枚举(ScopedEnums... 目录一、引言二、传统枚举类型的局限性2.1 命名空间污染2.2 整型提升问题2.3 类型转换问题三、C

C++链表的虚拟头节点实现细节及注意事项

《C++链表的虚拟头节点实现细节及注意事项》虚拟头节点是链表操作中极为实用的设计技巧,它通过在链表真实头部前添加一个特殊节点,有效简化边界条件处理,:本文主要介绍C++链表的虚拟头节点实现细节及注... 目录C++链表虚拟头节点(Dummy Head)一、虚拟头节点的本质与核心作用1. 定义2. 核心价值二

C++ 检测文件大小和文件传输的方法示例详解

《C++检测文件大小和文件传输的方法示例详解》文章介绍了在C/C++中获取文件大小的三种方法,推荐使用stat()函数,并详细说明了如何设计一次性发送压缩包的结构体及传输流程,包含CRC校验和自动解... 目录检测文件的大小✅ 方法一:使用 stat() 函数(推荐)✅ 用法示例:✅ 方法二:使用 fsee

Windows下C++使用SQLitede的操作过程

《Windows下C++使用SQLitede的操作过程》本文介绍了Windows下C++使用SQLite的安装配置、CppSQLite库封装优势、核心功能(如数据库连接、事务管理)、跨平台支持及性能优... 目录Windows下C++使用SQLite1、安装2、代码示例CppSQLite:C++轻松操作SQ