《算法竞赛从入门到国奖》算法基础:入门篇-递归初阶

💡Yupureki:个人主页

✨个人专栏:《C++》 《算法》


🌸Yupureki🌸的简介:


目录

递归初阶

[1. 汉诺塔](#1. 汉诺塔)

算法原理

实操代码

[2. 占卜DIY](#2. 占卜DIY)

算法原理

实操代码

[3. FBI 树](#3. FBI 树)

算法原理

实操代码


前言

递归:函数自己调用自己。自己调用自己就说明执行的代码时一致的,这样的作用就是解决许多个重复子问题。因此利用递归解决问题的关键,便是是否有重复的子问题

递归初阶

1. 汉诺塔

题目链接:

信息学奥赛一本通(C++版)在线评测系统

算法原理

这是一道很经典的递归问题,我们先从简单分析

假设n = 1,只有一个盘子,很简单,直接从a拿出来,放到b上即可

假设n = 2,那么我们就得借助b了,由于要遵循小盘子始终在大盘子上,那么可以这样做

  • 1号盘子放在b上
  • 2号盘子放在c上
  • 1号盘子放在c上

如果n=3呢?我们这样看三个盘子

我们把3上面的2个盘子当作一个整体,那么我们就相当于是要把新的1和2移到c上,这个步骤和n = 2是一致的

其中我们要移动新的1时,就相当于是n = 2移动到c的情况,只不过移动的目标位置可能是a或者b,初始位置也不是a

以此类推到n > 3时,我们都可以把某个盘子上面的部分当作一个新的整体,那么无非就又回到了n = 2的情况;这样就变成了多个相同的子问题来解决

设计递归函数

重复子问题:将x个盘子,从begin柱子转移到end柱子上,其中通过tmp柱子作为中转;

具体操作:

  • 先将beign上面x-1个盘子借助end转移到tmp上;
  • 再把begin最下面一个盘子放在end上;
  • 最后把tmp上面x-1个盘子借助begin转移到end上。
cpp 复制代码
// 将 x 个盘⼦从 begin 移动到 end
// 其中 tmp 是中转站
void move(int x, char begin, char tmp, char end)

实操代码

cpp 复制代码
#include <iostream>
using namespace std;
int n;
char a, b, c;
// 将 x 个盘⼦从 begin 移动到 end
// 其中 tmp 是中转站
void move(int x, char begin, char tmp, char end)
{
    if(x == 0) return;
    move(x - 1, begin, end, tmp);
    printf("%c->%d->%c\n", begin, x, end);
    move(x - 1, tmp, begin, end);
}

int main()
{
    cin >> n >> a >> b >> c;
    move(n, a, c, b); // 把 a 上⾯ n 个盘⼦,借助 c 转移到 b 上
    return 0;
}

2. 占卜DIY

题目链接:

P10457 占卜DIY - 洛谷

算法原理

整个模拟过程:

  1. 抽出第13堆最上面的牌c:
  2. 把第堆的最后一张拿出来;
  3. 拿到这张牌之后,重复2过程,直到拿到13

上述三步整个一循环,一直循环4次,直到把13堆上面的牌拿完。

重复子问题:

拿到一张牌x之后,把第x堆最后一张拿出来。

实操代码

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

vector<vector<char>> v(14, vector<char>(4));
vector<int> arr(14, 0);

int getnum(char ch)
{
    if (ch == '1')
        return 13;
    else if (ch >= '2' && ch <= '9')
        return ch - '0';
    else if (ch == '0')
        return 10;
    else if (ch == 'J')
        return 11;
    else if (ch == 'Q')
        return 12;
    else if (ch == 'A')
        return 1;
    else
        return 0;
}

void func(char ch)
{
    if (arr[0] == 4)
        return;
    int num;
    num = getnum(ch);
    arr[num]++;
    char code;
    if (num == 0)
        code = '1';
    else if (num == 13)
    {
        code = v[13][0];
        v[13].erase(v[13].begin());
    }
    else
    {
        code = v[num][v[num].size() - 1];
        v[num].erase(v[num].end()-1);
        v[num].insert(v[num].begin(), ch);
    }
    func(code);
}

int main()
{
    for (int i = 1; i <= 13; i++)
    {
        for (int j = 0; j < 4; j++)
        {
            char num; cin >> num;
            v[i][j] = num;
        }
    }
    func('1');
    int ret = 0;
    auto it = arr.begin() + 1;
    while(it != arr.end()-1)
    {
        if (*it == 4)
            ret++;
        it++;
    }
    cout << ret;
    return 0;
}

3. FBI 树

题目链接:

P1087 [NOIP 2004 普及组] FBI 树 - 洛谷

算法原理

重复子问题:处理每一棵子树:

  1. 确定出该子树的类型;
  2. 然后从中间分开,先处理左子树,再处理右子树;
  3. 然后打印该子树的类型

如何快速判断出该子树的类型?因为我们要求的是一段区间内1的个数,我们可以利用「前缀和」数组求出这段区间和,然后在查询某段区间时,判断一下此时的区间和:

  • 如果等于区间长度,说明是I类型;
  • 如果等于0,说明是B类型;
  • 否则就是F类型。

实操代码

cpp 复制代码
#include <iostream>
#include <string>
#include <vector>
using namespace std;

struct node {
    char val;
    node* left;
    node* right;
    node(char _val) {
        val = _val;
        left = nullptr;
        right = nullptr;
    }
};

string s;
vector<int> v(1, 0);

char check(int left, int right) {
    int ones = v[right] - v[left];
    int length = right - left;
    
    if (ones == length) return 'I';
    if (ones == 0) return 'B';
    return 'F';
}

node* create(int left, int right) {
    if (right - left == 1) {
        char ch = (s[left] == '1') ? 'I' : 'B';
        return new node(ch);
    }
    
    int mid = (left + right) / 2;
    node* newnode = new node(check(left, right));
    newnode->left = create(left, mid);
    newnode->right = create(mid, right); 
    
    return newnode;
}

void rearorder(node* root) {
    if (root == nullptr) return;
    rearorder(root->left);
    rearorder(root->right);
    cout << root->val;
}

int main() {
    int num; 
    cin >> num;
    cin >> s;
    
    for (int i = 0; i < s.size(); i++) {
        int num = s[i] - '0';
        v.push_back(num + v[i]);
    }
    
    node* root = create(0, s.size());
    rearorder(root);
    
    return 0;
}
相关推荐
亓才孓4 分钟前
[Class的应用]获取类的信息
java·开发语言
初願致夕霞5 分钟前
Linux_进程
linux·c++
开开心心就好12 分钟前
AI人声伴奏分离工具,离线提取伴奏K歌用
java·linux·开发语言·网络·人工智能·电脑·blender
Never_Satisfied15 分钟前
在JavaScript / HTML中,关于querySelectorAll方法
开发语言·javascript·html
2302_8138062218 分钟前
【嵌入式修炼:数据结构篇】——数据结构总结
数据结构
Thera77737 分钟前
【Linux C++】彻底解决僵尸进程:waitpid(WNOHANG) 与 SA_NOCLDWAIT
linux·服务器·c++
3GPP仿真实验室39 分钟前
【Matlab源码】6G候选波形:OFDM-IM 增强仿真平台 DM、CI
开发语言·matlab·ci/cd
Wei&Yan41 分钟前
数据结构——顺序表(静/动态代码实现)
数据结构·c++·算法·visual studio code
devmoon43 分钟前
在 Polkadot 上部署独立区块链Paseo 测试网实战部署指南
开发语言·安全·区块链·polkadot·erc-20·测试网·独立链
lili-felicity43 分钟前
CANN流水线并行推理与资源调度优化
开发语言·人工智能