零.【摘要】:在这个专刊里,我会把所有算法都讲一遍,这章讲了递归算法的原理和题目。
一.【递归算法】:当一个函数在其定义中调用自身时,称之为递归调用,这个函数被称为递归函数。递归算法是一种解决问题的重要方法,它可以将复杂的问题分解为较小的子问题,并通过使用函数的递归调用来解决这些子问题。
二.【操作步骤】:
首先,递归函数需要一个基本情况来终止递归,否则会导致无限循环。在该基本情况下,通过返回一个结果来停止递归的进行。
其次,递归函数需要在每次递归调用时将问题规模缩小,这样才能确保递归算法最终收敛到基本情况。
三.【递归阶乘】:
当写递归阶乘时,我们需要考虑基本情况和递归情况。
基本情况:阶乘的基本情况是当n为0或1时,阶乘的结果是1。
递归情况:当n大于1时,阶乘的结果可以通过将n乘以n-1的阶乘来计算。
下面是一个C++的示例代码来演示如何使用递归计算阶乘:
#include <iostream>
int factorial(int n) {
// 基本情况
if (n == 0 || n == 1) {
return 1;
}
// 递归情况
return n * factorial(n - 1);
}
int main() {
int n;
std::cout << "Enter a positive integer: ";
std::cin >> n;
// 检查输入是否为负数
if (n < 0) {
std::cout << "Error: Input must be a positive integer!" << std::endl;
return 0;
}
std::cout << "Factorial of " << n << " is " << factorial(n) << std::endl;
return 0;
}
在上面的代码中,factorial函数是一个递归函数,它接受一个整数参数n,并根据以下两种情况进行计算:
- 当n等于0或1时,这是基本情况,递归终止并返回结果1。
- 当n大于1时,这是递归情况,递归调用factorial函数并将问题规模缩小为n-1。最终,计算的结果是n与factorial(n-1)的乘积。
通过输入一个正整数n,上述代码将使用递归算法计算n的阶乘并输出结果。
但需要注意,递归算法的效率一般较低,因为需要不断调用函数,占用较多的栈空间。因此,在实际的编程中,应该谨慎使用递归算法,并且在可能的情况下优化为迭代算法。
【题目讲解】:
1:汉诺塔问题
时间限制: 1000 ms 内存限制: 65536 KB
提交数: 42515 通过数: 16506
【题目描述】
约19世纪末,在欧州的商店中出售一种智力玩具,在一块铜板上有三根杆,最左边的杆上自上而下、由小到大顺序串着由64个圆盘构成的塔。目的是将最左边杆上的盘全部移到中间的杆上,条件是一次只能移动一个盘,且不允许大盘放在小盘的上面。
这是一个著名的问题,几乎所有的教材上都有这个问题。由于条件是一次只能移动一个盘,且不允许大盘放在小盘上面,所以64个盘的移动次数是:18,446,744,073,709,551,615
这是一个天文数字,若每一微秒可能计算(并不输出)一次移动,那么也需要几乎一百万年。我们仅能找出问题的解决方法并解决较小N值时的汉诺塔,但很难用计算机解决64层的汉诺塔。
假定圆盘从小到大编号为1, 2, ...
【输入】
输入为一个整数(小于20)后面跟三个单字符字符串。
整数为盘子的数目,后三个字符表示三个杆子的编号。
【输出】
输出每一步移动盘子的记录。一次移动一行。
每次移动的记录为例如 a->3->b 的形式,即把编号为3的盘子从a杆移至b杆。
【输入样例】
2 a b c
【输出样例】
a->1->c
a->2->b
c->1->b
当盘子数为2时,输入为 2 a b c
,表示有两个盘子需要从柱子a移动到柱子b,借助柱子c。
初始状态:
a b c
-------------
2 1 -
-------------
步骤1:将编号1的盘子从柱子a移动到柱子c
a b c
-------------
2 - 1
-------------
步骤2:将编号2的盘子从柱子a移动到柱子b
a b c
-------------
- 2 1
-------------
步骤3:将编号1的盘子从柱子c移动到柱子b
a b c
-------------
- - 1
2
-------------
最终状态:
a b c
-------------
- - -
2 1
-------------
移动的记录为:
a->1->c
a->2->b
c->1->b
通过这些步骤,我们将两个盘子从柱子a移动到柱子b,借助柱子c。
【动图解释】:
【算法思路】:
我们定义了一个递归函数hanoi
,它接收4个参数:盘子的数目n,起始柱子from,目标柱子to,借助柱子aux。在每个递归调用中,我们做以下几步:
- 基本情况:若只有一个盘子,直接将其移动到目标柱子,并输出移动记录。
- 递归情况:先将n-1个盘子从from柱子通过to柱子借助aux柱子移动到aux柱子上(递归调用hanoi函数)。
- 将第n个盘子从from柱子移动到to柱子,并输出移动记录。
- 最后将n-1个盘子从aux柱子通过from柱子借助to柱子移动到目标柱子上(递归调用hanoi函数)。
通过输入盘子的数目和三个杆子的编号,上述代码将使用递归算法来解决汉诺塔问题,并输出每一步的移动记录。
需要注意的是,由于汉诺塔问题的复杂性,对于大数目的盘子,可能需要较长的时间来计算和输出移动记录。因此,在实际的应用中,应根据需要选择合适的盘子数目。
【代码实现】:
#include <iostream>
// 定义递归函数,移动n个盘子从from柱子到to柱子,借助aux柱子
void hanoi(int n, char from, char to, char aux) {
// 基本情况:只有一个盘子时,直接移动到目标柱子
if (n == 1) {
std::cout << from << "->" << n << "->" << to << std::endl;
return;
}
// 递归情况:先将n-1个盘子从from柱子通过to柱子借助aux柱子移动到aux柱子上
hanoi(n-1, from, aux, to);
// 将第n个盘子从from柱子移动到to柱子
std::cout << from << "->" << n << "->" << to << std::endl;
// 最后将n-1个盘子从aux柱子通过from柱子借助to柱子移动到to柱子上
hanoi(n-1, aux, to, from);
}
int main() {
int n;
char from, to, aux;
// 输入盘子的数目和杆子的编号
std::cout << "Enter the number of disks: ";
std::cin >> n;
std::cout << "Enter the IDs of the rods (from to aux): ";
std::cin >> from >> to >> aux;
// 调用递归函数解决汉诺塔问题
hanoi(n, from, to, aux);
return 0;
}
2:放苹果
时间限制: 1000 ms 内存限制: 65536 KB
提交数: 25738 通过数: 15842
【题目描述】
把M个同样的苹果放在N个同样的盘子里,允许有的盘子空着不放,问共有多少种不同的分法?(用K表示)5,1,1和1,5,1 是同一种分法。
【输入】
第一行是测试数据的数目t(0<=t<=20)。以下每行均包含二个整数M和N,以空格分开。1<=M,N<=10。
【输出】
对输入的每组数据M和N,用一行输出相应的K。
【输入样例】
1
7 3
【输出样例】
8
【算法思路】:
可以用一个递归函数来实。我们定义函数countDifferentWays(m, n)表示将m个苹果放入n个盘子中的不同分法数量。递归过程如下:
- 如果m=0,表示没有苹果需要放,那么只有一种分法,即所有盘子都为空,即返回1。
- 如果n=0,表示没有盘子可放苹果,这种情况下没有分法,即返回0。
- 如果m < n,表示苹果数量少于盘子数量,无法每个盘子都放苹果,所以也没有分法,即返回0。
- 否则,分为两种情况:
- 第一种情况是选择空盘子,即将问题转化为将m个苹果放入n-1个盘子中的分法数量,即返回countDifferentWays(m, n-1)。
- 第二种情况是至少有一个盘子放了苹果,即将问题转化为将m-n个苹果放入n个盘子中的分法数量,即返回countDifferentWays(m-n, n)。 因为至少有一个盘子放了苹果,所以我们可以从m个苹果中选出n个苹果放进去,然后剩下的苹果继续放入剩下的n个盘子。
- 将第一种和第二种情况的分法数量相加,即返回countDifferentWays(m, n-1) + countDifferentWays(m-n, n)。
【代码实现】:
#include <iostream>
using namespace std;
int countDifferentWays(int m, int n) {
if (m == 0) {
return 1;
}
if (n == 0 || m < n) {
return 0;
}
return countDifferentWays(m, n-1) + countDifferentWays(m-n, n);
}
int main() {
int t;
cin >> t;
while (t--) {
int m, n;
cin >> m >> n;
cout << countDifferentWays(m, n) << endl;
}
return 0;
}
3:因子分解时间限制: 1000 ms 内存限制: 65536 KB 提交数: 15821 通过数: 10060 【题目描述】输入一个数,输出其素因子分解表达式。 【输入】输入一个整数 n (2≤n<100)。 【输出】输出该整数的因子分解表达式。 表达式中各个素数从小到大排列。 如果该整数可以分解出因子a的b次方,当b大于11时,写做 【输入样例】60 【输出样例】2^2*3*5 |
【算法思路】:
- 首先判断输入的整数n是否为质数,如果是质数,直接输出n即可。
- 如果n不是质数,那么从2开始遍历,找到n的最小的因子i,即满足i能整除n的最小的数。
- 计算n除以i的商,得到的结果即为新的整数m。
- 递归调用自身,求解m的素因子分解表达式,得到结果str。
- 输出结果为i与str的拼接,如果m等于1,则直接输出i,否则输出i和"*"再拼接上str。
【代码实现】:
#include <iostream>
using namespace std;
string primeFactorization(int n) {
if (n <= 1) {
return "";
}
for (int i = 2; i <= n; i++) {
if (n % i == 0) {
int m = n / i;
string str = primeFactorization(m);
if (str == "") {
return to_string(i);
} else {
return to_string(i) + "*" + str;
}
}
}
return "";
}
int main() {
int n;
cin >> n;
string result = primeFactorization(n);
cout << result << endl;
return 0;
}
4:判断元素是否存在
时间限制: 1000 ms 内存限制: 65536 KB
提交数: 24981 通过数: 10213
【题目描述】
有一个集合M是这样生成的:(1) 已知k是集合M的元素; (2) 如果y是M的元素,那么,2y+1和3y+1都是M的元素;(3) 除了上述二种情况外,没有别的数能够成为M的一个元素。
问题:任意给定k和x,请判断x是否是M的元素。这里的k是无符号整数,x 不大于 100000,如果是,则输出YES,否则,输出NO。
【输入】
输入整数 k 和 x, 逗号间隔。
【输出】
如果是,则输出 YES,否则,输出NO。
【输入样例】
0,22
【输出样例】
YES
【算法思路】:
- 首先判断k是否等于x,如果相等,则x是集合M的元素,输出YES。
- 接下来判断x是否满足2x+1或3x+1是集合M的元素,如果满足则递归调用自身,判断2x+1或3x+1是否是集合M的元素。
- 如果2x+1或3x+1都不是集合M的元素,说明x不是集合M的元素,输出NO。
【代码实现】:
#include <iostream>
using namespace std;
bool checkExist(unsigned int k, unsigned int x) {
if (k == x) {
return true;
}
if (2*k+1 <= x && checkExist(2*k+1, x)) {
return true;
}
if (3*k+1 <= x && checkExist(3*k+1, x)) {
return true;
}
return false;
}
int main() {
unsigned int k, x;
char comma;
cin >> k >> comma >> x;
if (checkExist(k, x)) {
cout << "YES" << endl;
} else {
cout << "NO" << endl;
}
return 0;
}
5:数的计数(Noip2001)
时间限制: 1000 ms 内存限制: 65536 KB
提交数: 36933 通过数: 15891
【题目描述】
我们要求找出具有下列性质数的个数(包括输入的自然数n)。先输入一个自然数n(n≤1000),然后对此自然数按照如下方法进行处理:
不作任何处理;
在它的左边加上一个自然数,但该自然数不能超过原数的一半;
加上数后,继续按此规则进行处理,直到不能再加自然数为止。
【输入】
自然数n(n≤1000)。
【输出】
满足条件的数。
【输入样例】
6
【输出样例】
6
【提示】
【样例解释】
满足条件的数为如下所示:
6
16
26
126
36
136
【算法思路】:
- 首先判断当前的数字num是否满足条件,如果满足条件则计数加一。
- 将num的每一位数字按照规则进行处理,即在左边加上一个自然数并继续递归处理。
- 需要注意的是,自然数的大小不能超过原数的一半。
【代码实现】:
#include <iostream>
#include <cmath>
using namespace std;
int counter = 0;
void countNumbers(int n, int m, int num) {
if (m == 0) {
if (num <= n) {
counter++;
}
return;
}
for (int i = 0; i <= n/2; i++) {
int newNum = num + i * pow(10, m-1);
countNumbers(n, m-1, newNum);
}
}
int main() {
int n;
cin >> n;
int digits = to_string(n).size();
countNumbers(n, digits, 0);
cout << counter << endl;
return 0;
}
创作不易,点个👍,观个注吧。