算法4:递归算法

零.【摘要】:在这个专刊里,我会把所有算法都讲一遍,这章讲了递归算法的原理和题目。

一.【递归算法】:当一个函数在其定义中调用自身时,称之为递归调用,这个函数被称为递归函数。递归算法是一种解决问题的重要方法,它可以将复杂的问题分解为较小的子问题,并通过使用函数的递归调用来解决这些子问题。
二.【操作步骤】:
 

首先,递归函数需要一个基本情况来终止递归,否则会导致无限循环。在该基本情况下,通过返回一个结果来停止递归的进行。

其次,递归函数需要在每次递归调用时将问题规模缩小,这样才能确保递归算法最终收敛到基本情况。
三.【递归阶乘】:

当写递归阶乘时,我们需要考虑基本情况和递归情况。

基本情况:阶乘的基本情况是当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。在每个递归调用中,我们做以下几步:

  1. 基本情况:若只有一个盘子,直接将其移动到目标柱子,并输出移动记录。
  2. 递归情况:先将n-1个盘子从from柱子通过to柱子借助aux柱子移动到aux柱子上(递归调用hanoi函数)。
  3. 将第n个盘子从from柱子移动到to柱子,并输出移动记录。
  4. 最后将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个盘子中的不同分法数量。递归过程如下:

  1. 如果m=0,表示没有苹果需要放,那么只有一种分法,即所有盘子都为空,即返回1。
  2. 如果n=0,表示没有盘子可放苹果,这种情况下没有分法,即返回0。
  3. 如果m < n,表示苹果数量少于盘子数量,无法每个盘子都放苹果,所以也没有分法,即返回0。
  4. 否则,分为两种情况:
    • 第一种情况是选择空盘子,即将问题转化为将m个苹果放入n-1个盘子中的分法数量,即返回countDifferentWays(m, n-1)。
    • 第二种情况是至少有一个盘子放了苹果,即将问题转化为将m-n个苹果放入n个盘子中的分法数量,即返回countDifferentWays(m-n, n)。 因为至少有一个盘子放了苹果,所以我们可以从m个苹果中选出n个苹果放进去,然后剩下的苹果继续放入剩下的n个盘子。
  5. 将第一种和第二种情况的分法数量相加,即返回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时,写做 a^b ;当b等于11时,则直接写成a。

【输入样例】

60

【输出样例】

2^2*3*5

提交 统计信息 提交记录

【算法思路】:
 

  1. 首先判断输入的整数n是否为质数,如果是质数,直接输出n即可。
  2. 如果n不是质数,那么从2开始遍历,找到n的最小的因子i,即满足i能整除n的最小的数。
  3. 计算n除以i的商,得到的结果即为新的整数m。
  4. 递归调用自身,求解m的素因子分解表达式,得到结果str。
  5. 输出结果为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

提交 统计信息 提交记录

【算法思路】:

  1. 首先判断k是否等于x,如果相等,则x是集合M的元素,输出YES。
  2. 接下来判断x是否满足2x+1或3x+1是集合M的元素,如果满足则递归调用自身,判断2x+1或3x+1是否是集合M的元素。
  3. 如果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

提交 统计信息 提交记录

【算法思路】:

  1. 首先判断当前的数字num是否满足条件,如果满足条件则计数加一。
  2. 将num的每一位数字按照规则进行处理,即在左边加上一个自然数并继续递归处理。
  3. 需要注意的是,自然数的大小不能超过原数的一半。

【代码实现】:

#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;
}

创作不易,点个👍,观个注吧。

 

 

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值